排序演算法之堆排序—原地排序O(nlgn)
《演算法導論》第6章介紹堆排序(heapsort)像插入排序而不像合併排序,是一種原地(in place)排序演算法:任何時候,陣列中只有常數個元素儲存在輸入陣列之外。並且時間複雜度為O(nlgn)。 堆資料結構不只在堆排序中有用,還可以構成一個有效的優先佇列。
堆介紹
(二叉)堆資料結構是一種陣列物件。可以被視為一棵完全二叉樹。每個節點都有兩個子結點,每一層都是填滿的最後一層除外,因此可以用陣列表示即可。(《演算法導論》中指出,length為此陣列中的元素個數,heap_size為此陣列中堆的元素個數。就是說,heap_size以後的陣列元素不屬於堆內,不符合堆的要求。) 樹的根為A[1],給定一個數組位置i,它的父節點為i/2」,左兒子為2i,右兒子為2i+1。
堆實現
堆的高度為O(lgn),一些基本操作可以在O(lgn)內完成。 注:以下演算法假設陣列a下標從1開始,a[0]不參與計算,heap_size為陣列中堆的大小。
MAX-HEAPIFY過程,保持最大堆性質的關鍵
輸入一個數組A和下標i,並且假定以i為父節點的所有子結點滿足堆性質。 輸出使得i結點保持堆性質。
/**
* 輸入:預設A陣列中i後面的位置都滿足最大堆性質
* 輸出:使得i也滿足最大堆性質
* 時間複雜度:O(lgn)
*/
private static void MaxHeapify(int[] a, int i, int heap_size) {
int left = 2 * i; //左兒子
int right = 2 * i+1; //右兒子
int largest = i; //i和左兒子、右兒子中最大值的位置
//如果左兒子比i大,則把左兒子做為最大值
if( left <= heap_size && a[left] > a[i]) {
largest = left;
}
//如果右兒子比最大值大,則把右兒子做為最大值位置
if( right <= heap_size && a[right] > a[largest]) {
largest = right;
}
//如果最大值位置不是i,則將最大值位置的值與i位置的值交換,交換後需要被交換的子結點繼續保持堆性質
if ( largest != i) {
int tmp = a[i];
a[i] = a[largest];
a[largest] = tmp;
MaxHeapify(a, largest, heap_size);
}
}
BUILD-MAX-HEAP過程,可以在無序的陣列上構造堆
/**
* 自底向上的用MaxHeapify將一個數組變成最大堆
* 時間複雜度O(nlgn)
*/
private static void BuildMaxHeap(int[] a) {
//堆底為葉結點肯定都滿足性質,從非葉結點開始構建。
int heap_size = a.length - 1;
int i = heap_size/2;
for (; i>0; i--)
MaxHeapify(a, i, heap_size);
}
HEAPSORT過程,執行時間為O(nlgn)
排序演算法每次從堆頂取出最大值,與陣列末尾值交換即可,並縮小heap的大小,將末尾值排除在堆外。當堆縮小為1時,陣列已完成從小到大排序。
private static void HeapSort(int[] a) {
BuildMaxHeap(a);
for(int i=0;i<a.length;i++) {
System.out.println(a[i]);
}
int heap_size = a.length-1; //構造後的堆的heap_size為陣列大小
for(; heap_size > 0; heap_size--){//每次從堆頂取出最大值與堆末尾交換,並縮減堆大小
int tmp = a[heap_size];
a[heap_size] = a[1];
a[1] = tmp;
MaxHeapify(a, 1, heap_size-1);
}
}
優先順序佇列
《演算法導論》第6章第5節優先順序佇列中指出,“實際中快速排序的一個好的實現往往由於堆排序。儘管這樣,堆資料結構還是有著很大的用處:作為高效的優先順序佇列(priority queue)。”【檢視JAVA8原始碼可知,Arrays.sort也使用的快速排序(小於47個數的時候用的插入排序)。】 “最大優先順序佇列的一個應用是在一臺分時計算機上進行作業排程。當一個作業做完或被中斷時,用EXTRACT-MAX操作從所有等待的作業中,選擇出具有最高優先順序的作業”