堆排序
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;
}