堆
定義: 一種特別的樹狀數據結構.
一個堆滿足的特性:
- 給定堆中任意節點P和C,若P是C的母節點,那麼P的值會小於等於(或大於等於)C的值”。若母節點的值恆小於等於子節點的值,此堆稱爲最小堆(min heap)
- 若母節點的值恆大於等於子節點的值,此堆稱爲最大堆(max heap)
堆的實現
堆的實現通過構造二叉堆(binary heap),實爲二叉樹的一種
二叉樹:是每個節點最多隻有兩個分支(即不存在分支度大於2的節點)的樹結構.
而堆的實現是靠二叉樹中的 完全二叉樹實現的.
完全二叉樹:對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹.
完全二叉樹 如下圖 都是完全二叉樹:
非完全二叉樹
即:完全二叉樹滿足 每一層都是連續集中在左側;
此處用數組 來存儲完全二叉樹.
根節點的下表爲 1.
若有一個結點 i, (假設 i 結點有父節點且 有 左子樹和右子樹),則 其父節點的下標爲 i/2,左子樹 的 下標爲 2*i右子樹的下表爲 i*2+1.
有兩種 增加結點的方法.
1.從根結點( 1 )開始,然後 2, 3 . 每次增加一個結點後向上調整.
2. 先將總結點數的 一半 加在最底層(從左往右),然後 開始從倒數第二層開始,每次增加一個結點都向下調整.
以大根堆 爲例:
這裏就涉及二叉樹的一些性質了.
如果 ,則結點 i 是二叉樹的根,無雙親; 如果 i > 1,則其雙親爲 .
如果 ,則結點 i 無左孩子(結點 i 爲葉子結點);否則其左孩子是結點
如果 ,則結點 i 無右孩子,否則其右孩子是結點
向上調整:
每次調整前先判斷 這個結點是否爲 根結點 即.
if(i == 1) return ; // i 爲 待調整的點
若不是根節點 則判斷 該結點 與 該節點的父節點的大小,若 該結點大,則 該節點於父節點互換位置,繼續向上 判斷,直到 該節點 是父節點 或 該節點沒有 父結點 大 時退出.
while(i != 1)
{
if(h[i] < h[i/2]) swap(i,i/2);
else break;
i = i/2;
}
向下調整:
假設 待插入的結點爲 i.
每次 需要先判斷 i結點是否存在 左子樹,若存在 則進行判斷,不存在則退出.
用 變量 temp 備份當前 最大 的結點.
先 判斷 i 和 左兒子 的大小, 有temp 備份最大值.
判斷 i 是否有 右兒子,若有 則用 temp 與 i 的 右兒子 比較,用 t 備份最大值.
若沒有 右兒子則不用進行判斷
然後 判斷 i與 temp 是否相等. 若 (i == temp) 則代表 當前 i 是最小的 值,退出.
否則 交換 temp 與 i,繼續進行判斷
while(i * 2 <= n)
{
//判斷和左兒子的關係
if(h[i] < h[i*2]) t = i*2;
else temp = i;
//如果有 右兒子,
if(i*2+1 <= n)
{
if(h[temp] < h[i*2+1]) t = i*2+1;
}
if(temp != i)
{
swap(temp,i);
i = temp;
}
else break;
}
到此,已經 建立了一個滿二叉樹. 之後需要做的 每次將 根拿出來,然後將 二叉樹的 最後一個結點 放到 根的 位置,然後讓根 向下調整.
此處,將 根節點 拿出放到 數組的最後一個位置,待 排序後, 即爲 從小向大 排序.
void heapsort()
{
while(n > 1)
{
swap(1,n);
n--;
siftdown(1);
}
return ;
}
假設 堆 如下圖所示:
第一次 排序後 :
然後向下調整. 直到最後只剩下 根節點時結束.
#include<stdio.h>
// 用數組 模擬二叉樹
int h[101]; //用來存放堆的數組
int n; //存儲 堆 中的元素個數
void swap(int x,int y)
{
int temp = h[x];
h[x] = h[y];
h[y] = temp;
return ;
}
void siftdown(int i) //向下調整
{
int t; //記錄當前最大值
int flag = 0; //判斷是否需要繼續向下調整
while(i * 2 <= n)
// while(i * 2 <= n && flag == 0)
{
//判斷和左兒子的關係
if(h[i] < h[i*2]) t = i*2;
else t = i;
//如果有 右兒子,
if(i*2+1 <= n)
{
if(h[t] < h[i*2+1]) t = i*2+1;
}
if(t != i)
{
swap(t,i);
i = t; //更新 i 爲剛纔與它交換的兒子結點的編號,便於向下調整
}
else break;
/*else
{
flag = 1; //否則 說明當前的父節點 已經比兩個子節點都要大了,不需要繼續進行調整
}*/
}
return ;
}
/*void siftup(int i) //向上調整
{
int flag = 0;
if(i == 1) return ; //已經是 根結點了
while(i != 1 && flag == 0)
{
//判斷是否比父節點 小
if(h[i] < h[i/2]) swap(i,i/2);
else flag = 1;
i = i/2;
}
return ;
}
*/
void heapsort()
{
while(n > 1)
{
swap(1,n);
n--;
siftdown(1);
}
return ;
}
void creat()
{
int i;
for(i = n/2;i >= 1;i--) siftdown(i);
return ;
}
int main()
{
int num,i;
scanf("%d",&num);
n = num;
for(i = 1;i <= num;i++) scanf( "%d",&h[i]);
creat(); //建堆 大頂堆
//堆排序
// heapsort();
for(i = 1;i <= num;i++) printf( "%d ",h[i]);
return 0;
}