【演算法導論】6.堆排序
堆排序時間複雜度為O(nlgn), 空間複雜度為O(1).
應用:最大堆用於堆排序,最小堆用於構造優先佇列。
6.1堆
二叉堆是一個數組,可被看作一個近似的完全二叉樹。除了最底層外,該樹是完全滿的。
所以可以計算得到父結點、左孩子和右孩子的下標。
Parent(i)
return i/2
Left(i)
return 2i
Right(i)
return 2i+1.
6.2維護堆的性質
Max-Heapify通過使A[i]的值在最大堆中逐級下降,使以下標i為根結點堆子樹重新遵循最大堆的性質
//虛擬碼 Max-Heapify(A,i)//A為輸入陣列,i為下標 { l=Left(i); r=right(i); if l<=A.heap-size and A[l]>A[i] largest=l; else largest=I; if r<=A.heap-size and A[largest]>A[i] largest=r; if largest!=i exchange A[i] with A[largest]; Max-Heapify(A,largest); }
這段程式碼通過將下標為i的值與其左孩子和右孩子的值進行比較,並將最大下標存在largest中。如果A[i]為最大值,程式結束。如果A[i]小於其左孩子或者右孩子,那麼,首先將A[i]與A[largest]進行交換,將最大值換到根(也就是A[i]的位置)。此時,本來的A[i]存在A[largest]的位置,要考慮這時以該結點(換下來的A[i])為根結點的子樹是否滿足最大堆的性質,所以需要遞迴呼叫Max-Heapify.
維護一個堆的時間複雜度為O(lgn),即O(h),h為樹高。實際上就是,在最壞情況下,最小的結點開始存放在根結點,需要換到最底部,走過的長度就是樹高。每一次交換的時間複雜度為常數。
6.3建堆
Build-Max-Heap(A)
{
A.heap-size=A.length;
for i=A.length/2 down 1
Max-Heapify(A,i);
}
以上程式碼通過對每個非葉子結點呼叫Max-Heapify,把一個大小為n=A.length的陣列轉換為最大堆。
時間複雜度為O(n),即把一個無序陣列構造成最大堆的時間為O(n)
將上述程式碼修改為Build-Min-Heap,即將第三行修改為Min-Heapify即可構造成一個最小堆。
6.4堆排序演算法
初始化堆:利用Build-Max-Heap將輸入A[1..n]構建成最大堆。
將根結點的元素放到正確位置:由於構造的是最大堆,整個陣列中最大的元素一定在A[1]位置,通過將它與A[n]交換,可以將該元素放到正確的位置(這個正確的位置指的是排序後,最大的元素放在最後)。
去掉排好的結點:從堆中去掉此時放好的n(可通過減少A.heap-size實現)。
維護剩下的結點:通過呼叫Max-Heapify(A,1),在A[1..n-1]上構造一個新的最大堆。
迴圈:重複以上過程,直到堆的大小從n-1降到2.
HeapSort(A)
{
Build-Max-Heap(A);//先建堆
for i=A.length downto 2
{
exchange A[1] with A[i];
A.heap-size--;
Max-Heapify(A,1);
}
}
時間複雜度為O(nlgn),因為每次呼叫Build-Max-Heap的時間複雜度為O(n),而n-1次呼叫Max-Heapify,每次的時間為O(lgn)。
6.5優先佇列
優先佇列分為:最大優先佇列和最小優先佇列。優先佇列是一種用來維護由一組元素構成的集合S的資料結構,其中每個元素都有一個相關的值,稱為關鍵字。
優先佇列的操作:
Insert(S,x):把元素x插入集合S。
Maximum(S):返回S中具有最大鍵值的元素。
Extract-Max(S):去掉並返回S中具有最大鍵值的元素。
Increase-Key(S,x,k):將元素x的值增加到k。
最大優先佇列的應用:共享計算機系統的作業排程,最大優先佇列用於記錄將要執行的各個作業以及它們的相對優先順序。
最小優先佇列的應用:被用於基於事件驅動的模擬器。
用堆來實現優先佇列:
Maximum: 該程式碼在O(1)時間內實現Maximum操作。(前提是最大堆已經建好)
Heap-Maximum(A)
return A[1];
Extract-Max:(類比於HeapSort)
時間複雜度為O(lgn). 將最大值儲存在max中,把A[A.heap-size]的值換到A[1],然後重新維護一個最大堆。
Heap-Extract-Max(A)
{
if A.heap-size<1
return error;
max=A[1];
A[1]=A[A.heap-size];
A.heap-size--;
Max-Heapify(A,1);
return max;
}
Increase-Key:在將元素A[i]值從x增大到k時,可能會使其違反最大堆的性質。所以需要在從當前結點到根結點到路徑上,為新增的關鍵字尋找恰當的插入位置。在進行Increase-Key的操作中,當前元素通過不斷與其父結點進行比較,判斷是否需要與父結點交換位置,直到當前元素的值小於其父結點。
時間複雜度為O(lgn).在更新關鍵字值時,更新結點到根結點的路徑長度為O(lgn).
Heap-Increase-Key(A,x,key)
{
if key<A[i];
return error;//這裡需要key值大於A[i],即x
A[i]=key;
while i>1 and A[parent(i)]<A[i]
{
exchange A[i] with A[parent(i)];
i=parent(i);
}
}
Insert:輸入為要被插入到最大堆中的A中的新元素的關鍵字。首先通過增加一個關鍵字為極小值的葉結點來擴充套件最大堆。然後呼叫Heap-Increase-Key為新結點設定對應的關鍵字,同時保持最大堆的性質。時間複雜度為O(lgn).
Max-Heap-Insert(A,key)
{
A.heap-size++;
A[A.heap-size]=-10000000;
Heap-Increase-Key(A,A.heap-size,key);
}
在包含一個n個元素的堆中,所有優先佇列的操作的時間複雜度都為O(lgn).