/* ary是存储记录的数组, start是需要调整为大顶堆的根记录下标, end是 它的最后一个叶子记录的下标。 注意,传入的start到end之间的记录,除去根记录,根记录的左右子二叉树都 是大顶堆, 要完全符合大顶堆的性质调用此函数才有效。 下面函数要做的就是调整以start为根记录,end为最后一个叶子记录的完全二叉树为大顶堆。 */ void Heapify(int ary[], unsigned int start, unsigned int end) { unsigned int left = 0; unsigned int right = 0; unsigned int max = 0; left = start * 2; right = start * 2 + 1; /* 如果存在左子记录 */ while (left <= end) { /* 左右子记录中,先默认左子记录的关键字值最大,保存其下标 */ max = left; /* 如果左右子记录都存在,改写max使其保存了拥有最大关键字值记录的下标 */ if (right <= end) { if (ary[left] < ary[right]) { max = right; } else { max = left; } } /* 现在max中是保存了左右子记录中(假设存在)关键字值最大的下标 */ if (ary[start] < ary[max]) { /* 根记录的关键字值小于左右子记录中关键字值最大的 */ ary[0] = ary[max]; ary[max] = ary[start]; ary[start] = ary[0]; /* 从被交换的子记录中开始调整 */ start = max; } else { /* 二叉树符合大顶堆性质, 调整结束 */ break; } left = start * 2; right = start * 2 + 1; } /* 调整到最二叉树的最后一个叶子结点上, 二叉树也符合大顶堆性质, 调整结束 */ } /* 进行堆排序, 完成后ary中的记录下标从1到size,它们的关键字值是非递减的 */ void HeapSort(int ary[], unsigned int size) { /* 建堆 */ BuildHeap(ary, size); for (unsigned int i = size; i > 0; i--) { /* 把根记录和最后一个叶子记录交换 */ ary[0] = ary[1]; ary[1] = ary[i]; ary[i] = ary[0]; /* 调整从1到i- 1组成的二叉树为大顶堆。 注意: 交换只是破坏了以ary【1】为根的二叉树大顶堆性质, 它的左右 子二叉树还是具 备大顶堆性质。 */ Heapify(ary, 1, i - 1); } } /* 调整整棵二叉树(完全无序状态)使之成为大顶堆。 策略: 首先这课二叉树的结构是完全二叉树, 我们可以从最后一个非叶子记录调整, 直到整棵二叉树的根记录。 比如二叉树有size个结点记录, 那么最后一个非叶子结点的下标就 是size / 2(取整), 我们就从size / 2, size / 2 - 1, ... , 直到调整以1为下标的整棵二叉树为大顶堆。 因为以最后一个叶子记录为根的二叉树必定符合调用函数Heapify的条件, 位于同一层的非叶子结点必定也符合调用条件,Heapify函数处理完后这一层后, 上一层的非叶子记录对应的二叉树也符合了调用条件, 这样直到以整棵二叉树的根(即ary【1】)记录为根的二叉树也符合大顶堆的性质, 整个大顶堆就建立起来。 现在整棵二叉树的根记录就是该记录集中关键字值最大的记录。 */ void BuildHeap(int ary[], unsigned int size) { /* 传入下标为i(size / 2 >= i >= 1)的记录为根, 下标为size的最后一个叶子记录的二叉树进行调整 */ for (unsigned int i = size / 2; i > 0; i--) { Heapify(ary, i, size); } } /* 算法分析: 1.堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成, 它们均是通过调用Heapify实现的。 堆排序的最坏时间复杂度为O(n * lgn)。堆排序的平均性能较接近于最坏性能。 BuildHeap最坏情况下时间复杂度为O(n),但是for循环在最坏情况下却 要O(n * lgn)的时间。 2.由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。 3.堆排序是就地排序,辅助空间为O(1)。 4.它是不稳定的排序方法。 */