堆排序
1. 堆定义
堆是一种特殊的二叉树,具备以下两种性质
1)每个节点的值都大于(或小于,称为最小堆)其子节点的值
2)树是完全平衡的,并且最后一层的树叶都在最左边
二叉堆是一种完全二叉树,其任意子树的左右节点(如果有的话)的键值一定比根节点大,下图是一个最小堆。
二叉堆的表示结构:
由于二叉堆的结构性质,可以采用一维数组来表示二叉堆。
二叉堆元素从数组下标1开始,则
T[i]的左儿子为T[2*i],右儿子为T[2*i+1]
T[i]的父结点为T[i/2]
2.堆的操作
2.1 插入
将待插入的元素放在数组的最后位置,这可能破坏了二叉堆结构性质,通过“上浮”操作维护其结构。
2.2 删除
将数组的第一位元素删除,并将数组最后位置的元素放到第一位,这可能破坏其结构性质,可通过“下沉”操作维护二叉堆的结构。
上浮操作:
void swap(int* _a, int* _b)
{
int tmp =*_a;
*_a = *_b;
*_b = tmp;
}
/*
* 从底向上维护二叉堆中第n位元素的堆序性质
* 如果第n位元素的值大于其父结点n/2的值,则交换
* 直到根结点或满足父结点值小于当前节点值
**/
void shiftUp(int* T, int n)
{
assert(T !=NUU && n>=1);
int i = n;
int p =i/2;
while(i !=1)
{
p =i/2;
//不满足最小堆结构性质,交换当前结点与其父结点值
//指向当前结点的父结点,继续判断是否满足堆序性质
if(T[i]< T[p])
{
swap(&T[i],&T[p]);
i= p;
}else{
return;
}
}
}
下沉操作:
/*
* 自顶向下维护二叉堆的堆序性质
* 如果第i位元素没有子结点,则结束
* 如果第i位元素有子结点,则c为值较小子结点的下标
* 如果T[i]>T[c],则交换i和c位置的元素值,i=c,继续进行
**/
void shiftDown(int* T, int n)
{
assert(T !=NULL && n >= 1);
int i = 1;
int c;
do
{
c =2 * i;
if(c> n)
return;
//有两个子结点,找到值小的子结点
if((c+1)<= n)
{
if(T[c+1]< T[c])
c++;
}
if(T[i]> T[c])
{
swap(&T[i],&T[c]);
i= c;
}else{
return;
}
} while(c <= n);
}
3.堆排序
有了二叉堆及其操作,可以据其定义堆排序算法。
未排序堆 | 已排序部分 |
1 i n
堆排序主要包括两个部分:
a.建立堆
通过循环的shiftUp操作,将原数组构造成堆结构;
b.排序
数组分成两部分,未排序的堆和已排序部分。初始时,已排序部分为空。
此时T[1]为前i个元素中最小的,将T[1]和T[i]交换,这样就把剩下数组中最小的元素放到已排序部分;再通过shiftDown操作维护堆序性质;i值从n到2循环
void heapSort(int* T, int len)
{
assert(T !=NULL && len >= 0);
int i;
for(i=2;i<=len; ++i)
{
shiftUp(T,i);
}
for(i=len; i>=2; –i)
{
swap(&T[i],&T[1]);
shiftDown(T,i-1);
}
}
int main()
{
int a[] = {-1,8,4,2,5,3,7,6,9,1};
printf(“before heapSort:\n”);
int i, len = 9;
for(i=0; i<len+1; i++)
printf(“%d “,a[i]);
printf(“\n”);
heapSort(a, len);
printf(“after heapSort:\n”);
for(i=0; i<len+1; i++)
printf(“%d “,a[i]);
printf(“\n”);
return 0;
}