堆排序
根据http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html改编而来
堆的定义
n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆。
情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小顶堆)
情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大顶堆)
其中i=1,2,…,n/2向下取整;
若将和此序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。
由此可以得到,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
堆排序的思想
利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
其基本思想为(大顶堆):
1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。
构造初始堆:将R[1..n]构造为堆;
初始化堆是对所有的非叶子结点进行筛选。最后一个非终端元素的下标是[n/2]向下取整,所以筛选只需要从第[n/2]向下取整个元素开始,从后往前进行调整。
比如,给定一个数组,首先根据该数组元素构造一个完全二叉树。
然后从最后一个非叶子结点开始,每次都是从父结点、左孩子、右孩子中进行比较交换,交换可能会引起孩子结点不满足堆的性质,所以每次交换之后需要重新对被交换的孩子结点进行调整。
进行堆排序:将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。
有了初始堆之后就可以进行排序了。
排序开始,首先输出堆顶元素(因为它是最值),将堆顶元素和最后一个元素交换,这样,第n个位置(即最后一个位置)作为有序区,前n-1个位置仍是无序区,对无序区进行调整,得到堆之后,再交换堆顶和最后一个元素,这样有序区长度变为2。。。
不断进行此操作,将剩下的元素重新调整为堆,然后输出堆顶元素到有序区。每次交换都导致无序区-1,有序区+1。不断重复此过程直到有序区长度增长为n-1,排序完成。
堆排序实例
首先,建立初始的堆结构如图:
然后,交换堆顶的元素和最后一个元素,此时最后一个位置作为有序区(有序区显示为黄色),然后进行其他无序区的堆调整,重新得到大顶堆后,交换堆顶和倒数第二个元素的位置……
重复此过程:
最后,有序区扩展完成即排序完成:
堆排序最重要的三个功能函数,伪代码如下:
堆调整:
void heapAdjust(int[] a, int start, int end) {
int startV = a[start];
for (int i = 2*start + 1; i <= end; i *= 2) {
if (i<end && a[i]<a[i+1]) i++; // 将索引指向较大的孩子节点
if (a[i]<startV) break; // 如果父节点的值大于孩子节点,调整完成
a[start] = a[i]; // 父节点的值小于孩子节点,二者交换,并继续调整后继堆
start = i;
}
a[start] = startV;
}
初始化堆:
void heapInit(int[] a, int n) {
// 初始化从最后一个非终端节点开始调整,直到第0个节点;这样保证在调整每一个节点时,其左右孩子节点都是合格的堆
for (int i = n/2; i >= 0; i--) heapAdjust(a, i, n);
}
排序:
void heapSort(int[] a, int n) {
heapInit(a, n); // 第一步要求对堆进行初始化调整
for (int i = n; i >= 0; i--) {
swap(a[0], a[i]); // 交换a[0]和堆中的最后一个元素
heapAdjust(a, 0, i - 1); // 调整堆的0~i-1,i已经在有序序列中
}
}