1、初識堆排序
二叉堆的特性:
- 最大堆的堆頂是整個堆中的最大元素
- 最小堆的堆頂是整個堆中的最小元素
以最大堆爲例,如果刪除一個最大堆的堆頂(並不是完全刪除,而是跟末尾的點交換位置),經過自我調整,第2大的元素就會被交換上來,成爲最大堆的新堆頂,如下圖:
如上圖所示,在刪除值爲10的堆頂節點後,經過調整,值爲9的新節點就會頂替上來;在刪除值爲9的堆頂節點後,經過調整,值爲8的新節點就會頂替上來……
由於二叉堆的這個特性,每一次刪除舊堆頂,調整後的新堆頂都是大小僅次於舊堆頂的節點。那麼只要反覆刪除堆頂,反覆調整二叉堆,所得到的集合就會成爲 一個有序集合,過程如下:
- 刪除節點9,節點8成爲新堆頂
- 刪除節點8,節點7成爲新堆頂
- 刪除節點7,節點6成爲新堆頂
- 刪除節點6,節點5成爲新堆頂
- 刪除節點5,節點4成爲新堆頂
- 刪除節點4,節點3成爲新堆頂
- 刪除節點3,節點2成爲新堆頂
經過上述步驟,原本的最大二叉堆已經變成了一個從小到大的有序集合。
二叉堆實際存儲在數組中,數組中的元素排列如下:
由此,歸納出堆排序算法的步驟:
- 把無序數組構建成二叉堆。需要從小到大排序,則構建成最大堆;需要從大到小排序,則構建成最小堆。
- 循環刪除堆頂元素,替換到二叉堆的末尾,調整堆產生新的堆頂。
2、堆排序代碼實現
Code:
import java.util.Arrays;
public class TreeNode5 {
/**
* “下沉”調整
* @param array 待調整的堆
* @param parentIndex 要“下沉”的父節點
* @param length 堆的有效大小
*/
public static void downAdjust(int[] array, int parentIndex, int length) {
// temp 保存父節點值,用於最後的賦值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length) {
// 如果有右孩子,且右孩子大於左孩子的值,則定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {
childIndex++;
}
// 如果父節點大於任何一個孩子的值,則直接跳出
if (temp >= array[childIndex])
break;
//無須真正交換,單向賦值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
/**
* 堆排序(升序)
* @param array 待調整的堆
*/
public static void heapSort(int[] array){
//1、把無序數組構建成最大堆
for(int i = (array.length-2)/2;i >= 0;i--){
downAdjust(array,i,array.length);
}
System.out.println("無序數組構建成的最大堆:" + Arrays.toString(array));
//2、循環刪除堆頂元素,移到集合尾部,調整堆產生新的堆頂
for(int i = array.length - 1;i > 0;i--){
//最後1個元素和第一個元素進行交換
int temp = array[i];
array[i] = array[0];
array[0] = temp;
//“下沉”調整最大堆
downAdjust(array,0,i);
}
}
public static void main(String[] args) {
int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
System.out.println("排序前:"+ Arrays.toString(array));
heapSort(array);
System.out.println("排序後:"+ Arrays.toString(array));
}
}
編譯輸出:
3、堆排序總結
排序算法的步驟:
- 把無序數組構建成二叉堆;
- 循環刪除堆頂元素,並將該元素移到集合尾部,調整堆產生新的堆頂:
首先把無序數組構建成二叉堆,這一步的時間複雜度是O(n);
然後進行n-1次循環;每次循環調用一次downAdjust方法,計算規模是 (n-1)×logn ,時間複雜度爲O(nlogn)。
兩個步驟是並列關係,所以整體的時間複雜度是O(nlogn)。
堆排序和快速排序的區別和聯繫:
相同點:堆排序和快速排序的平均時間複雜度都是O(nlogn),並且都是不穩定排序;
不同點:
- 快速排序的最壞時間複雜度是O(n²),而堆排序的最壞時間複雜度穩定在O(nlogn);
- 快速排序遞歸和非遞歸方法的平均空間複雜度都是O(logn),而堆排序的空間複雜度是O(1)。
———————————————————————————————————————
內容來源:《漫畫算法》
關注公衆號,回覆 【算法】,獲取高清算法書!