排序算法之堆排序
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。
大根堆:每个结点的值都大于其左孩子和右孩子结点的值。所以对任一棵子树:根节点的值都是最大的。
小根堆:每个结点的值都小于其左孩子和右孩子结点的值。所以对任一棵子树:根节点的值都是最小的。
堆可以采用数组存储,结点的标号从 $0$ 开始,则对任一个结点 $i$,其左孩子的结点标号为 $2i+1$,右孩子的结点标号为 $2i+2$。
下面以大根堆为例,介绍堆排序的基本步骤。
1)首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端。
2)将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为 $n-1$。
3)将剩余的 $n-1$ 个数再构造成大根堆,再将顶端数与 $n-1$ 位置的数交换,如此反复执行,便能得到有序数组(一开始就是数组存储的)。
可见这个算法最重要的操作就是:将树调整为大顶堆。
对某个子树进行堆化(Heapify)的前提是:除自身外,该子树的任一棵子树都满足大顶堆结构。
因为 BuildHeap 的过程是自底向上,所以将一棵乱序的树调整为大顶堆的过程可以调用 Heapify,换句话说:将一棵树调整
为大顶堆之前,其任一棵子树就都已经被调整为大顶堆了。
/*
* n: 树的结点总数
* i: 对以 i 为根节点的子树做堆化操作
* 之所以可以使用递归,必须有一个前提条件: 树 i 的任一棵子树都是大顶堆。
这样至上而下的调整时只需考虑下面的堆结构是否被破坏,而不用担心上面的结构是否被破坏。
*/
void Heapify(int tree[], int n, int i)
{
if (i >= n) return;
int c1 = (2 * i) + 1;
int c2 = (2 * i) + 2;
int max = i;
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
// 孩子结点的值比父节点的大,需要交换
if(max != i) {
swap(tree[max], tree[i]);
// 被交换后的子节点,其堆结构可能被破坏,继续向下调整
Heapify(tree, n, max);
}
}
void BuildHeap(int tree[], int n)
{
int last_node = n - 1;
int parent = (last_node - 1) / 2;
// 因为是从下到上调整的,所以满足 Heapify 的那个前提
for(int i = parent; i >= 0; --i) {
Heapify(tree, n, i);
}
}
void HeapSort(int tree[], int n)
{
// 先调整成一个堆
BuildHeap(tree, n);
for(int i = n - 1; i >= 0; --i) {
swap(tree[0], tree[i]);
Heapify(tree, n - 1, 0);
}
}
浙公网安备 33010602011771号