轉載自http://www.cppblog.com/guogangj/archive/2009/10/29/99729.html
首先說說數據結構概念——堆(Heap),其實也沒什麼大不了,簡單地說就是一種有序隊列而已,普通的隊列是先入先出,而二叉堆是:最小先出。
這不是很簡單麼?如果這個隊列是用數組實現的話那用打擂臺的方式從頭到尾找一遍,把最小的拿出來不就行了?行啊,可是出隊的操作是很頻繁的,而每次都得打一遍擂臺,那就低效了,打擂臺的時間複雜度爲Ο(n),那如何不用從頭到尾fetch一遍就出隊呢?二叉堆能比較好地解決這個問題,不過之前先介紹一些概念。
完全樹(Complete Tree):從下圖中看出,在第n層深度被填滿之前,不會開始填第n+1層深度,還有一定是從左往右填滿。
再來一棵完全三叉樹:
這樣有什麼好處呢?好處就是能方便地把指針省略掉,用一個簡單的數組來表示一棵樹,如圖:
那麼下面介紹二叉堆:二叉堆是一種完全二叉樹,其任意子樹的左右節點(如果有的話)的鍵值一定比根節點大,上圖其實就是一個二叉堆。
你一定發覺了,最小的一個元素就是數組第一個元素,那麼二叉堆這種有序隊列如何入隊呢?看圖:
假設要在這個二叉堆裏入隊一個單元,鍵值爲2,那隻需在數組末尾加入這個元素,然後儘可能把這個元素往上挪,直到挪不動,經過了這種複雜度爲Ο(logn)的操作,二叉堆還是二叉堆。
那如何出隊呢?也不難,看圖:
出隊一定是出數組的第一個元素,這麼來第一個元素以前的位置就成了空位,我們需要把這個空位挪至葉子節點,然後把數組最後一個元素插入這個空位,把這個“空位”儘量往上挪。這種操作的複雜度也是Ο(logn),比Ο(n)強多了吧?
常用堆的幾個功能(一下都是以小頂堆爲例(即結點的值都比左右孩子的小))
1)維護堆,向下更新
void min_heapify(int i) //以i爲根,向下更新,使得數組成爲小頂堆
{
int l = i << 1; //左孩子位子
int r = l + 1; //右孩子位子
int min = i;
if(l <= length && heap[l] < heap[min]) //比較大小
{
min = l;
}
if(r <= length && heap[r] < heap[min])
{
min = r;
}
if(i != min) //如果最小的值不是i節點,就交換
{
int tmp = heap[i];
heap[i] = heap[min];
heap[min] = tmp;
min_heapify(min); //繼續往下更新
}
}
2)維護堆,向上更新
void sinkup(int i) //以i爲根,向上更新,使得數組成爲小頂堆
{
int parent = i >> 1; //找父節點
if(heap[parent] > heap[i]) //交換
{
int tmp = heap[i];
heap[i] = heap[parent];
heap[parent] = tmp;
if(parent > 1)
sinkup(parent);//繼續向上更新
}
}
3) 建堆
void min_build() //建堆
{
for(int i = length / 2;i >= 1;i--) //length爲數組長度
{
min_heapify(i); //從length / 2開始向上更新,不信繪圖看看
}
}
4) 取最小值,即隊頭元素a[1]
int popheap()
{
int min ;
min=heap[1]; //取隊頭元素
heap[1] = heap[length]; //把尾元素放到頭
length--;
min_heapify(1); //向下更新,維護堆
return min;
}
5)進堆
void pushheap(int x)
{
length++;
heap[length] = x; // 插到數組最後一個
sinkup(length); //向上更新,維護堆
}