1、堆
二叉堆数据结构可以被看作一颗完全二叉树。对堆中元素i有如下关系:
i/2为元素i的父节点,2i为元素i的左子节点,2i+1为元素i的右子节点。
最大堆:对每个元素i都有i小于等于其父节点,大于等于其子节点。根节点为最大值。
最小堆:对每个元素i都有i大于等于其父节点,小于等于其子节点。根节点为最小值。
2、堆排序
假设一个序列<a1,a2,...,an>是一个最大堆,我们可以知道a1为序列的最大值。假如交换a1和an后还能使序列<a1,...,an-1>变成一个最大堆,则可以利用最大堆的特性来进行序列的排序。因此能够使用堆排序的关键在于建立最大堆或最小堆。
堆排序步骤:
1、将序列<a1,a2,...,an>构造成最大堆;
2、交换堆顶元素a1和堆尾元素an,得到序列:<a1',a2',...,an-1',an'>,其中an'为序列中的最大元素
3、将<a1',a2',...,an-1'>视为新的序列,重复步骤1、2,每次能够得到一个现有序列中的最大值,可以知道当n-1次操作后新序列已经排好序。
最大堆的构造:
可以知道一个最大堆,其左右子节点也是最大堆。因此上面堆排序步骤的第三步中,新序列的左右子节点是最大堆。实际上新序列重新构造成最大堆只需要很小的调整。
1、最大堆的调整
假设一个序列<a1,a2,...,an>,已知对元素ai,有其左右子节点为最大堆,比较ai,ai/2和a1/2+1的大小,有三种情况:
当ai为最大值时,此时ai及其子节点已经是最大堆了。
当ai/2为最大值时,交换ai和ai/2,可以知道ai为ai下子节点,子子节点的最大值,新的元素ai/2'有其其左右子节点为最大堆,对ai/2'进行向i一样的操作,当执行到最底端的元素时,便构成了最大堆。
当ai/2+1为最大值时,与ai/2为最大值一样。
2、初始序列最大堆的构造
我们知道一颗元素为n的完全二叉树,对第n/2后的元素并没有子节点,即可以将n/2后的元素视为最大堆。则通过n/2到1的循环可以逐步从完全二叉树的底部用1中的方法构建最大堆。
3、堆排序算法(c语言)
void swap(int *a, int *b)
{
int x = *a;
*a = *b;
*b = x;
}
void HeapAdjust(int a[], int i, int length)
{
int left = 2*i; //i的左节点
int right = 2*i + 1; //i的右节点
int k = i;
if(k > length/2) //当i〉length/2时,i不存在左右节点
return;
if(a[left - 1] > a[k - 1]) //注意对堆的序列是从1开始,但c语言数值的初始序号为0,故堆序列对应数组序列需要减1
k = left;
if(right <= length && a[right - 1]> a[k - 1]) //i<length/2时,i节点可能不存在右节点
k = right;
if(k != i) {
swap(&a[i - 1], &a[k - 1]);
HeapAdjust(a,k,length); //k为与i节点交换元素的子节点,递归调整k节点
}
}
void HeapBuild(int a[], int length)
{
int i;
for(i = length/2; i >= 1; i--) { //从length/2到1逐步调整数组可以得到最大堆。
HeapAdjust(a, i, length);
}
}
void HeapSort(int a[], int length)
{
int i;
HeapBuild(a, length); //最大堆建立后,a[0]为最大值
for(i = length; i > 1; i--) { //length到1的循环,得到有序数值
swap(&a[0], &a[i - 1]);
HeapAdjust(a, 1, i - 1);
}
}
4、时间复杂度
对一个n位数组,堆的调整最坏情况需要递归log(n)(以2为底)次,堆的建立需要调用n次堆调整,之后的堆排序还需要调用n次堆首元素和尾元素的交换以及堆调整,时间复杂度为nlog(n)。