堆排序(最大堆)-----選擇排序的一種
前記:
堆排序只要理解了原理其實並不難,我在寫這個演算法時,按照理解一次性就成功了☺
一些前置知識:
1.首先應該明確的是堆(二叉堆)是一顆完全二叉樹;
因為二叉堆是一顆完全二叉樹,所以可以用陣列來儲存堆中的每一個元素,而且可以通過某個節點在陣列中的下標(i)很方便的找到其左右孩子節點的下標以及其父親節點的下標,如下圖所示:
由上圖還可以推出一個非常有用的性質:
假設我們用陣列arr來儲存二叉堆,則:
最後一個葉子節點的索引為arr.length-1
最後一個非葉子節點的索引為:(arr.length-1-1)/2
2.大根堆和小根堆:
大根堆:堆中所有父節點都大於其左右子節點的堆
小根堆:堆中所有父節點都小於其左右子節點的堆
或者按照下面的方式進行定義[
堆的定義:n個關鍵字序列array[0,…,n-1],當且僅當滿足下列要求:(0 <= i <= (n-1)/2)
① array[i] <= array[2*i + 1] 且 array[i] <= array[2*i + 2]; 稱為小根堆;
② array[i] >= array[2*i + 1] 且 array[i] >= array[2*i + 2]; 稱為大根堆;
3.由陣列生成大根堆(該過程又叫heapify)
n個節點的完全二叉樹array[0,…,n-1],最後一個節點n-1是第(n-1-1)/2個節點的孩子。對第(n-1-1)/2個節點為根的子樹調整,使該子樹稱為堆。
對於大根堆,調整方法為:若【根節點的關鍵字】小於【左右子女中關鍵字較大者】,則交換。
之後向前依次對各節點((n-1-1)/2 - 1)~ 0為根的子樹進行調整,看該節點值是否大於其左右子節點的值,若不是,將左右子節點中較大值與之交換,交換後可能會破壞下一級堆,於是繼續採用上述方法構建下一級的堆,直到以該節點為根的子樹構成堆為止。
反覆利用上述調整堆的方法建堆,直到根節點。
4.大根堆排序的原理
①將存放在array[0,…,n-1]中的n個元素建成初始堆;
②將堆頂元素與堆底元素進行交換,則序列的最大值即已放到正確的位置;
③但此時堆被破壞,將堆頂元素向下調整使其繼續保持大根堆的性質,再重複第②③步,直到堆中僅剩下一個元素為止。
我寫的大堆排序演算法如下:
package com.nrsc.sort;
public class HeapSort {
public static void main(String[] args) {
int[] arr = { 49, 38, 65, 97, 23, 22, 76 , 1, 5, 8, 2, 0, -1, 22 };
heapSort(arr);
System.out.println("排序後:");
for (int i : arr) {
System.out.println(i);
}
}
private static void heapSort(int[] arr) {
// 1. 根據原始陣列建立最大堆
buildMaxHeap(arr);
// 建立完最大堆之後需要對堆頂和堆底的元素進行調換
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, i, 0); // 將堆頂元素和堆底元素進行調換
// 調換後,此時最大元素已經位於堆底
// 而此時除堆底之外不滿足最大堆的元素正是堆頂的元素,所以對堆頂的元素
// 進行調整使其滿足最大堆的性質就完成了除去堆底元素外其他元素構建最大堆的工作
adjustDownToUp(arr, 0, i);
}
}
/**
* 建立初始最大堆
*
* @param arr
*/
private static void buildMaxHeap(int[] arr) {
int lastParent = (arr.length - 1) / 2;// 最後一個非葉子節點
// 從最後一個非葉子節點向前遍歷,使所有非葉子節點滿足最大堆的性質
for (int i = lastParent; i >= 0; i--) {
// arr.length表示要構建最大堆的陣列長度
adjustDownToUp(arr, i, arr.length);
}
}
/**
* 調整堆中的節點使其滿足最大堆
*
* @param arr
* @param i
* @param length
*/
private static void adjustDownToUp(int[] arr, int i, int length) {
int left = 2 * i + 1; // 左孩子
int right = 2 * i + 2;// 右孩子
int largest = i;// 先假設最大的為父節點
// 如果左子節點大於父節點,則最大值下標(largest)改為左子節點的下標
if (left < length && arr[left] > arr[i]) {
largest = left;
}
// 如果右子節點大於父節點和左子節點中的最大值,則將最大值下標改為右子節點的
if (right < length && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是父節點,而是左子節點或右子節點中的一個
// 就需要將其與父節點進行對換
if (i != largest) {
swap(arr, i, largest);
// 假設左子樹與父節點進行了交換,則以左子樹為根節點的二叉樹可能也會不滿足二叉堆
// 因此還必須對其進行調整
adjustDownToUp(arr, largest, length);
}
}
/**
* 進行陣列中元素的交換
*
* @param arr
* @param i
* @param largest
*/
private static void swap(int[] arr, int i, int largest) {
int tmp = arr[i];
arr[i] = arr[largest];
arr[largest] = tmp;
}
}