堆排序
這講的目的不在於堆排序畢竟快排足夠好,而在於數據結構堆。
所謂堆其實是指用一顆完全二叉樹維護的一維數組(簡單來說就是用一個一位數組儲存一顆完全二叉樹)。完全二叉樹請自行百度
堆有着非常鮮明的特點,就是知道一個節點在數組中儲存的位置後可以輕鬆得到其左右子葉(如果有的話)以及其父節點、相鄰節點(兩個節點有相同的父節點則爲相鄰節點)的儲存位置
堆的儲存方式
首先給一顆完全二叉樹標號,規則爲從上至下從左至右
然後將節點的數據按照標號-1(因爲數組從第0位開始)存入數組a
堆的索引
例如上圖中4號節點儲存在a[3],其父節點儲存在a[1],其左右子葉在a[7]、a[8],相鄰節點存在a[4]
有如下規律:
k號節點存在a[k-1],其父節點存在a[k div 2 -1] (div代表求商)
其左右子節點存在a[2*(k-1)+1],a[2*(k-1)+2]。
k號節點的相鄰節點存在與k相鄰的位置
堆排序
大頂堆(小頂堆)
大頂堆:每個結點的值都大於或等於其左右孩子結點的值
小頂堆:每個結點的值都小於或等於其左右孩子結點的值
堆排序思路
- 將待排序數列儲存到數組a中
- 將a看作一個完整二叉樹的數組儲存構建堆
- 將該完整二叉樹改造成大頂堆(小頂堆)
- 取出a[0]位的根節點(當前最大值/最小值)
- 重複第2-4步直到排序完成
- 升序使用大頂堆,降序使用小頂堆(方便模塊化)
代碼實現
以升序爲例
void change(int* a, int f, int s) {//交換數組中兩元素位置
int temp;
temp = a[f];
a[f] = a[s];
a[s] = temp;
}
主程序負責讀入輸出以及調用建堆函數
int main()
{
int n, i;
int a[100];
scanf("%d", &n);
for (i = 0; i < n; ++i)
scanf("%d", &a[i]);
for(i=n-1;i>0;--i) {//總共要取出n-1個數
set_heap(a,i+1);//構建大頂堆,利用i縮小每次構建堆的規模
change(a,0,i);//取出最大項放隊尾
}
for (i = 0; i < n; ++i)
printf("%d ", a[i]);
}
構建大頂堆
void set_heap(int* a, int p) {//構建大頂堆
for (int i = p / 2 - 1; i >= 0; --i) {//p/2-1指找到第一個有子葉的節點
if (i * 2 + 1 < p && a[i] < a[i * 2 + 1]) {//判別左子葉
change(a, i, i * 2 + 1);
if (((i * 2 + 1) * 2 + 1 < p && a[i * 2 + 1] < a[(i * 2 + 1) * 2 + 1]) || ((i * 2 + 1) * 2 + 2 < p && a[i * 2 + 1] < a[(i * 2 + 1) * 2 + 2])) {
set_heap(a, p);
return;
}
}
if (i * 2 + 2 < p && a[i] < a[i * 2 + 2]) {//判別右子葉
change(a, i, i * 2 + 2);
if (((i * 2 + 2) * 2 + 1 < p && a[i * 2 + 2] < a[(i * 2 + 2) * 2 + 1]) || ((i * 2 + 2) * 2 + 2 < p && a[i * 2 + 2] < a[(i * 2 + 2) * 2 + 2])) {
set_heap(a, p);
return;
}
}
}
}
以左子葉判別爲例
if (i * 2 + 1 < p && a[i] < a[i * 2 + 1])
表示若左子葉存在且值比當前節點高,則:
change(a, i, i * 2 + 2);交換父子節點
if (((i * 2 + 1) * 2 + 1 < p && a[i * 2 + 1] < a[(i * 2 + 1) * 2 + 1])
|| ((i * 2 + 1) * 2 + 2 < p && a[i * 2 + 1] < a[(i * 2 + 1) * 2 + 2]))
表示如果交換後左葉子自己爆炸了(左葉子的左葉子存在且值比左葉子高或是左子葉的右子葉存在且值比左子葉高我自己都有點亂),則:
set_heap(a, p);
return;只能回到底層重新開始建堆
。。。完結