heapsort中的数组索引

时间:2012-10-22 13:15:51

标签: c heapsort

在阅读Jon Bentley的“编程珍珠”第2章的第14章中,我理解堆使用基于单的数组,而C中最简单的方法是声明x [n + 1]并浪费元素x [0 ](第148页)。

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

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

Here是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开头时才会存在。

令我震惊的是implementation中的分析部分使用了以索引1开头的数组,而实现部分使用了以索引0开头的数组,并没有浪费第一个元素。

我在这里遗漏了什么吗?

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

稍微更改了原始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的实现中。万岁!

3 个答案:

答案 0 :(得分:11)

堆元素可以从索引0或索引1开始存储,决定使用哪个是由你决定的。

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

如果根位于索引0处,那么您将获得这些关系:

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

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

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

答案 1 :(得分:2)

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

答案 2 :(得分:0)

回答一点旧帖,认为我的小贡献可能有助于未来的访客。

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

考虑 Qiang Xu 链接和 interjay 基于零的索引逻辑。 这是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;
}