8

在阅读 Jon Bentley 的“Programming Pearls”第 2 版的第 14 章时,我了解到堆使用从一开始的数组,而 C 中最简单的方法是声明 x[n+1] 并浪费元素 x[0](第148)。

在第 157 页,Jon 列出了完整的堆排序伪代码:

for i = [2, n]
    siftup(i)
for (i = n; i >= 2; i--)
    swap(1, i)
    siftdown(i - 1)

是 C 中的一个实现。但是,数组索引从 0 开始,而不是 1。

void heapSort(int numbers[], int array_size)
{
  int i, temp;

  // Qiang: shouldn't the stop-condition be i >= 1?
  for (i = (array_size / 2)-1; i >= 0; i--)
    siftDown(numbers, i, array_size);

  for (i = array_size-1; i >= 1; i--)
  {
    // Qiang: shouldn't the swap be done with numbmers[1], instead of numbers[0]?
    temp = numbers[0];
    numbers[0] = numbers[i];
    numbers[i] = temp;
    siftDown(numbers, 0, i-1);
  }
}

void siftDown(int numbers[], int root, int bottom)
{
  int done, maxChild, temp;

  done = 0;
  while ((root*2 <= bottom) && (!done))
  {
    if (root*2 == bottom)
      maxChild = root * 2;
    else if (numbers[root * 2] > numbers[root * 2 + 1])
      maxChild = root * 2;
    else
      maxChild = root * 2 + 1;

    if (numbers[root] < numbers[maxChild])
    {
      temp = numbers[root];
      numbers[root] = numbers[maxChild];
      numbers[maxChild] = temp;
      root = maxChild;
    }
    else
      done = 1;
  }
}

我担心的是,如果数组从索引 0 开始,那么以下属性将不成立(如 Jon 书中第 148 页所写):

leftchild(i) = 2*i
rightchild(i) = 2*i+1
parent(i) = i/2

在我看来,这里的属性仅在 i 以 1 开头时才成立。

让我印象深刻的是,实现中的分析部分使用了索引1开始的数组,而实现部分使用了索引0开始的数组并且没有浪费第一个元素。

我在这里错过了什么吗?

编辑 在 interjay 的帮助下,我意识到了原始实现中包含的错误,可以使用 {66,4,23,4,78,6,44,11,22,1,99} 的测试输入数组来显示。

对原siftDown()函数稍作改动,调整父索引与其子索引的关系:

void siftDown(int numbers[], int root, int bottom)
{
  int done, maxChild, temp;

  done = 0;
  while ((root*2 + 1 <= bottom) && (!done))
  {
    if (root*2 + 1 == bottom ||
        numbers[root * 2 + 1] > numbers[root * 2 + 2])
      maxChild = root * 2 + 1;
    else
      maxChild = root * 2 + 2;

    if (numbers[root] < numbers[maxChild])
    {
      temp = numbers[root];
      numbers[root] = numbers[maxChild];
      numbers[maxChild] = temp;
      root = maxChild;
    }
    else
      done = 1;
  }
}

学分去 interjay,:-)

后记:看起来相同的错误没有出现在wikibooksalgorithmist的实现中。万岁!

4

3 回答 3

12

堆元素可以从索引 0 或索引 1 开始存储,使用哪个决定取决于您。

如果根元素位于索引 1 处,则父索引和子索引之间的数学关系很简单,如您在上面所示,因此许多书籍选择以这种方式教授它。

如果根在索引 0 处,你会得到这些关系:

leftchild(i) = 2*i+1
rightchild(i) = 2*i+2
parent(i) = (i-1) / 2

只要你保持一致,你选择哪一个并不重要。

您显示的 C 代码对我来说似乎不正确。它从数组索引 0 开始,但使用适合从索引 1 开始的父/子关系。

于 2012-10-22T13:25:16.867 回答
2

堆排序的可重用实现希望从 0 的根索引开始,以便用户可以使用普通(基于 0)的数组。您不想要求用户分配一个额外的成员并在索引 1 处开始数组,以便他们可以使用您的 heapsort 函数。您确实需要使用@interjay 显示的修改后的父/子计算。

于 2012-10-22T13:45:48.410 回答
0

回复小老帖子,觉得我的小贡献可能会对未来的访客有所帮助。

如果我错过了任何场景,请专家验证并纠正我的逻辑。

考虑了强旭链接和基于零的索引逻辑。这是 C# 代码,并使用以下输入进行了测试。

//------------------------------------------------ -------------------------------------------------- ---------------------------------------------

// 输入数组:

int[] ErrCaseArry = new int[] { 66, 4, 23, 4, 78, 6, 44, 11, 22, 1, 99}; 
int[] GenCaseArry = new int[] { 30, 20, 40, 10, 90, 160, 140, 100, 80, 70 };

int[] NearlySortedArry  = new int[] { 1, 2, 3, 4, 6, 5 };
int[] FewSortedArry1 = new int[] { 3, 2, 1, 4, 5, 6 }; 
int[] FewSortedArry2 = new int[] { 6, 2, 3, 1, 5, 4 };

int[] ReversedArry1  = new int[] { 6, 5, 4, 3, 2, 1 }; 
int[] FewDuplsArry2  = new int[] { 1, 3, 1, 2, 1, 3 }; 
int[] MoreDuplsArry3 = new int[] { 1, 1, 2, 2, 1, 2 };

//------------------------------------------------ -------------------------------------------------- ---------------------------------------------

public void HeapSort(int[] listToSort)
{
    int LastChildIndex = listToSort.Length -1;
    int parentElementIndex = ((LastChildIndex - 1)/ 2);

    //1. Use this loop to Construct Heap Array (Max/Min) by using Heapify function on every node.
    while (parentElementIndex >= 0)                                        //  (N - 1) / 2  to 0
    {
        Heapify(listToSort, parentElementIndex, LastChildIndex);           //  (N - 1) / 2  & Lenght - 1

        parentElementIndex--;
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    AppendArrayToResultString("Max Heap\t", listToSort);

    //2. Heap sort algorithm takes largest element off the heap and places it at the end of an array.
    //   This phase continue until all the elements are placed in the array that are in sorted order.
    int sortedElementIndex = listToSort.Length - 1;

    //-----------------------------------------------------------------------------------------------------------------------------------------------

    // In this loop get Largest Element to Zero'th postion and move to end. and reduce the loop count from Heapify Array. So that elements gets sorted from right.

    while (sortedElementIndex >= 0)                                       //  (N - 1) to 1
    {
        // Swap the elements (root(maximum value)) of the heap with the last element of the heap
        Swap(ref listToSort[0], ref listToSort[sortedElementIndex]);

        // sortedElementIndex-- : Decrease the size of the heap by one so that the previous max value will stay in its proper placement
        sortedElementIndex--;

        if (sortedElementIndex == -1) break;

        // Since largest elemented from 0 to last, Re Heapify and get the remaining largest element and place it in 0 position.
        Heapify(listToSort, 0, (sortedElementIndex));                 //  0 to (N - 1) 
    }

    //-----------------------------------------------------------------------------------------------------------------------------------------------
}

//Heapify() function maintain the heap property (Max Heap or Min Heap). Can be recursive or can use iteration loop like while/for.
void Heapify(int[] listToSort, int parentIndext, int lastChildIndext)
{
    //bool doneFlag = false;

    int largestElementIndex = 0;
    int leftChildIndex = parentIndext * 2 + 1;
    int rightChildIndex = parentIndext * 2 + 2;

    while (leftChildIndex <= lastChildIndext) //&& !doneFlag)
    {
        // If leftChild is larger than rightChild or it is the last child and there is no rightChild for this parent. 
        // Then consider leftChild as largestElement else consider rightChild as largestElement.
        if (leftChildIndex == lastChildIndext || listToSort[leftChildIndex] > listToSort[rightChildIndex]) 
        {
            largestElementIndex = leftChildIndex;
        }
        else
        {
            largestElementIndex = rightChildIndex;
        }

        //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        // If largestElement is larger than parent then swap them and make parent as largestElement to continue the loop.

        if (listToSort[parentIndext] < listToSort[largestElementIndex])
        {
            // Make largestElement as parent. And continue finding if childs (left and right) are bigger than element in largestIndex position.
            Swap(ref listToSort[parentIndext], ref listToSort[largestElementIndex]);

            // Repeat to continue sifting down the child now
            parentIndext = largestElementIndex;
            leftChildIndex = ((parentIndext * 2) + 1);
            rightChildIndex = ((parentIndext * 2) + 2);
        }
        else
        {
            //doneFlag = true; 
            break;  // Trying to avoid extra flag condition check. Or return.
        }
    }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------

void Swap(ref int num1, ref int num2)
{
    int temp = num1;

    num1 = num2;
    num2 = temp;
}
于 2014-08-20T15:30:00.917 回答