資料結構與演算法——堆排序
阿新 • • 發佈:2020-12-19
目錄原文連結:https://jiang-hao.com/articles/2020/algorithms-algorithms-heap-sort.html
演算法介紹
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法:
- 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序演算法中用於升序排列;
- 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序演算法中用於降序排列;
堆排序的平均時間複雜度為 Ο(nlogn)。
通常堆是通過一維陣列來實現的。在陣列起始位置為0的情形中:
- 父節點i的左子節點在位置[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8KrqGOd1-1608382007509)(https://wikimedia.org/api/rest_v1/media/math/render/svg/3bff8f7d580269fe6c1e35648032bf2b93354088)];
- 父節點i的右子節點在位置[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-BA6P27HJ-1608382007512)(https://wikimedia.org/api/rest_v1/media/math/render/svg/e14787fdbf6c5580fcd2cf9f63c21dbeb8d82f5e
- 子節點i的父節點在位置[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-XdeaMqve-1608382007513)(https://wikimedia.org/api/rest_v1/media/math/render/svg/f38b28cfa0a788a6d767061ab7481da190b339b6)];
在堆的資料結構中,堆中的最大值總是位於根節點(在優先佇列中使用堆的話堆中的最小值位於根節點)。堆中定義以下幾種操作:
- 最大堆調整(Max Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
- 建立最大堆(Build Max Heap):將堆中的所有資料重新排序
- 堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算
演算法步驟
首先第一步和第二步,建立堆,這裡我們用最大堆;建立過程中,保證調整堆的特性。
從最後一個分支的節點開始從右往左,從下至上進行調整為最大堆。
現在得到的最大堆的儲存結構如下:
初始堆建立完成。
接著,最後一步,堆排序,進行(n-1)次迴圈。
持續整個過程直至最後一個元素為止。
這個迭代持續直至最後一個元素即完成堆排序步驟。
演算法實現
public class HeapSort {
private int[] arr;
public HeapSort(int[] arr) {
this.arr = arr;
}
/**
* 堆排序的主要入口方法,共兩步。
*/
public void sort() {
/*
* 第一步:將陣列堆化
* beginIndex = 第一個非葉子節點。
* 從第一個非葉子節點開始即可。無需從最後一個葉子節點開始。
* 葉子節點可以看作已符合堆要求的節點,根節點就是它自己且自己以下值為最大。
*/
int len = arr.length - 1;
int beginIndex = (arr.length >> 1)- 1;
for (int i = beginIndex; i >= 0; i--)
maxHeapify(i, len);
/*
* 第二步:對堆化資料排序
* 每次都是移出最頂層的根節點A[0],與最尾部節點位置調換,同時遍歷長度 - 1。
* 然後從新整理被換到根節點的末尾元素,使其符合堆的特性。
* 直至未排序的堆長度為 0。
*/
for (int i = len; i > 0; i--) {
swap(0, i);
maxHeapify(0, i - 1);
}
}
private void swap(int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 調整索引為 index 處的資料,使其符合堆的特性。
*
* @param index 需要堆化處理的資料的索引
* @param len 未排序的堆(陣列)的長度
*/
private void maxHeapify(int index, int len) {
int li = (index << 1) + 1; // 左子節點索引
int ri = li + 1; // 右子節點索引
int cMax = li; // 子節點值最大索引,預設左子節點。
if (li > len) return; // 左子節點索引超出計算範圍,直接返回。
if (ri <= len && arr[ri] > arr[li]) // 先判斷左右子節點,哪個較大。
cMax = ri;
if (arr[cMax] > arr[index]) {
swap(cMax, index); // 如果父節點被子節點調換,
maxHeapify(cMax, len); // 則需要繼續判斷換下後的父節點是否符合堆的特性。
}
}
/**
* 測試用例
*
* 輸出:
* [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
*/
public static void main(String[] args) {
int[] arr = new int[] {3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6};
new HeapSort(arr).sort();
System.out.println(Arrays.toString(arr));
}
}