堆排序
堆排序不同于其他排序算法,它的时间复杂度为O(n lgn),与并归排序相同。但是,不同于并归排序的是,空间复杂度为O(1)。堆排序具有空间原址性。
堆
一种完全二叉树.它又被分为大根(顶)堆和小根(顶)堆.实际以数组存储但是,你可以将一个堆看成一个完全二叉树
大顶堆
若想从小到大排序就需要构建大顶堆,否则构建小顶堆。
顺序储存
由图可以清楚的看出大顶堆的性质——一颗根节点大于其余节点的完全二叉树。而小顶堆的性质与大顶堆正好相反,根节点比其他节点都要小。
维护堆的性质
要想建立一个堆首先需要知道如何维护堆的性质。编写一个函数,它接受要一个数组a和一个下标。
void max_heap_adj(int a[], int s, int m)
{
int l = s * 2; // 得到当前节点的左孩子的下标
int r = l + 1; // 右孩子的下标
int lagest = s;
if(l <= m && a[l] > a[s]) {
lagest = l;
}
if(r <= m && a[r] > a[lagest]) {
lagest = r;
}
// 比较当前节点和左孩子,右孩子之间的最大值.得到后将他们交换
if(lagest != s) {
std::swap(a[lagest], a[s]);
heapadjust(a, lagest, m);
// 由于交换后,以lagest为节点的子树可能会违反最大堆的性质,因此需要递归调用.
}
}
在程序的每一步中,从a[s], a[l], a[r]中选出最大的,并将下标存在lagest中。如果a[s]是最大的,不需要过多操作,过程结束。否则,交换a[s], a[lagest]的值。从而使s节点及其孩子节点都满足最大堆的性质。不过,在交换后,原来根节点的子树可能会违反最大堆性质,因此需要递归调用。
建堆
我们可以利用过程max_heap_adj来把一个大小为n的数组转换为最大堆。由于子数组a[n/2] + 1 …. a[n]中的元素都是树的叶子节点,因此从a[n/2]开始自下而上的转化。
void create_heap(int a[], int m)
{
int i;
for(i = m / 2; i >= 0; i--) {
heapadjust(a, i, m);
}
}
利用这个过程就可以得到大顶堆了。
堆排序
void heapsort(int a[], int n)
{
int i;
create_heap(a, n - 1);
for(i = n - 1; i >= 1; i--) {
std::swap(a[0], a[i]);
heapadjust(a, 0, i - 1);
}
}
初始的时候,算法利用create_heap将输入的数组建成最大堆。因为数组中最大的元素总在根节点(也就是数组的第一个元素a[0]),通过把它与a[0]交换,我们可以让该元素放到正确的位置上。这时,我们再从堆中去掉该节点(这步操作通过减少数组的元素来实现)
剩余的节点中,原来根的孩子节点,仍是大根堆,而新的根节点可能会违背大根堆的性质。为了维护其性质,需要不断地调用max_heap_adj,从而在a[0 … n-1]上构造一个新的大根堆。