用过多次STL中的partial_sort算法,却全然不知其实现原理,也没跟进STL源码中去深究。某天在电话面试的时候被问到它的实现原理,没能答出来,实在惭愧。阅读partial_sort源代码以后,有种似曾相识的感觉,哦~~原来是堆排序。以前只在书本上了解过堆排序,没去认真了解和实现过,着实不知道它的妙处所在,如今灾难降临,给自己敲响警钟。
堆排序:
关键字序列k[1...n]当满足ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ n/2 )这一条件时,称该序列为堆序列,将此序列看成是完全二叉树的话,则该二叉树满足这一条件:树中任一非叶结点的关键字不大于或不小于它的左右孩子的关键字。
因此要实现堆排序应先解决两个问题: 1.创建满足条件的堆MakeHeap; 2.对堆进行排序HeapSort。
奇怪,创建堆的时候不是根据条件已经排好序了吗?条件中仅仅说明非叶结点和左右子结点之间的关系,并没有说到左右子结点之间的关系,所以,还必须再排序。那要怎么排?画出二叉堆后很容易看出来,根部关键字肯定最大(或最小),所以,只需将其与序列末端的结点交换,交换以后,破坏了原有堆性质,必须重新创建新堆,当然,除去刚交换过后的“最值”结点(它不用再参与堆排序),如此反复,将会得到一个有序的序列。以下为创建小根堆和堆排序的代码:
void AjustHeap(int *arr, int len, int k) //调整堆
{
int th = 2*k+2; //定位序号为k结点的左右孩子
for (; th < len; th = 2*th+2) //当结点的孩子也为非叶结点时,继续调整以该孩子为根部的小堆
{
if (arr[th] > arr[th - 1])
{
th--;
}
if (arr[k] > arr[th])
{
swap(arr[k], arr[th]);
}
}
if (th == len && arr[k] > arr[th - 1])
{
swap(arr[k], arr[th - 1]); //非叶结点只有一个孩子(必定在序列最后)的情况
}
}
void MakeHeap(int *arr, int len) //创建堆
{
for (int i = len/2; i > 0;) //从最后一个非叶结点开始
{
--i;
AjustHeap(arr, len, i, arr[i]);
}
}
void PopHeap0(int *arr, int len) //将根部结点交换到最后
{
swap(*arr, *(arr + len - 1));
}
void HeapSort(int *arr, int len) //堆排序
{
MakeHeap(arr, len);
for (int i = len; i > 1; --i)
{
PopHeap0(arr, i);
MakeHeap(arr, i - 1);
}
}