堆排序
最近學習了堆排序算法。堆排序是一種選擇排序,是不穩定的排序,其最壞、平均、最優時間複雜度都爲O(nlogn)。堆排序邏輯上是利用完全二叉樹對數進行移動,實際是對數組進行操作。
堆排序算法的基本思路:
- 根據升序或降序將數組邏輯上轉換成大頂堆或小頂堆;
- 將根節點的數和最後的數交換位置,“沉”在數組的最後,然後繼續調整成大頂堆/小頂堆;
- 反覆執行步驟2,直至結束。
看了上述步驟之後可能會有這兩個問題:1.什麼是大頂堆和小頂堆?2.爲什麼對數組操作,會跟二叉樹有關係?
- 首先來解釋大頂堆和小頂堆的問題:
大頂堆:對一顆二叉樹而言,如果每個節點的值都大於或等於其左右子節點的值,則稱爲大頂堆;
小頂堆:對一顆二叉樹而言,如果每個節點的值都小於或等於其左右子節點的值,則稱爲小頂堆;
注意:沒有要求節點的左右子節點值的大小關係 - 爲什麼對數組操作,會跟二叉樹有關係?
堆排序實際上是在對數組進行操作,但是其邏輯思想是利用完全二叉樹來移動數據。首先將一個無序數組按順序排列成一棵完全二叉樹,例如數組arr:{8,6,7,4,3,2,5,1,9},排列成一棵完全二叉樹:
- 然後從最後的非葉子節點開始,將這棵完全二叉樹調整成大頂堆;
- 再將根節點和最後的節點互換位置,將最大的數放到了數組的最後:
- 將9放到數組末端之後,將不再參與下一輪排序。重新將剩餘的數調整爲大頂堆。依次類推,直至結束。
其代碼實現如下:
package com.tree.heapSort;
import java.util.Arrays;
/*
* 堆排序
* 將數組從小到大排序爲例:
* 說明:邏輯上是將數組按順序排列成一個完全二叉樹進行操作,實際上是對數組的操作
* 1.構建大頂堆
* 2.將最開始的節點和末尾節點互換位置,將最大元素“沉”到最後,也就是放在數組的最末尾,然後繼續反覆調整+交換
*/
public class HeapSortDemo2 {
public static void main(String[] args) {
// 生成一個無序的數組
int[] arr = new int[10];
int num = 0;
int count = 0;
boolean flag = false;
while (count < arr.length) {
num = (int) (Math.random() * 100);
for (int i = 0; i < arr.length; i++) {
if (arr[i] == num) {
flag = true;
break;
}
}
if (!flag) {
arr[count++] = num;
} else {
flag = false;
}
}
System.out.println("原數組爲:" + Arrays.toString(arr));
// 堆排序
heapSort(arr);
}
/**
* 堆排序
*
* @param arr
*/
public static void heapSort(int[] arr) {
// 1.將數組轉換成大頂堆
// 從最後的非葉子節點開始自下向上調整
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
System.out.println("數組轉換成大頂堆後:" + Arrays.toString(arr));
// 2.將頂端的元素與末尾元素置換位置,然後再繼續調整成大頂堆
int temp = 0;
int count = 0;
for (int j = arr.length - 1; j > 0; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
// 交換後較小的元素又被放在了根節點,需要重新調整成大頂堆
adjustHeap(arr, 0, j);
System.out.println("第" + (++count) + "輪堆排序後:" + Arrays.toString(arr));
}
System.out.println("堆排序最終結果:" + Arrays.toString(arr));
}
/**
* 將數組調整爲大頂堆
*
* @param arr 帶調整的數組
* @param i 每次需要調整子樹的父節點下標
* @param length 每次需要調整的元素個數,遞減
*/
public static void adjustHeap(int[] arr, int i, int length) {
// 將待調整的元素臨時保存
int temp = arr[i];
// 開始調整
// 從下標i開始,比較其左右子節點的值是否大於arr[i],如果大於,就和arr[i]互換位置,構建局部大頂堆
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
// k表示下標i元素的左子節點,k+1表示其右子節點,首先判將k移動到較大值的位置
if (k + 1 < length && arr[k] < arr[k + 1]) {// 說明右子節點大於左子節點
k++;// 移動到右子節點
}
// 判斷下標k的值是否大於待調整元素,如果大於,就arr[k]=arr[i]
if (arr[k] > temp) {
arr[i] = arr[k];
// 賦值完畢後,要將i移動到k的位置,下一輪比較該位置和其左右子節點的大小
i = k;
} else {
break;
}
}
// 循環結束時,說明已經將最初下標爲i的父節點的樹調整爲大頂堆,並且此時i的位置已經移動
// 需要將temp填在arr[i]的位置
arr[i] = temp;
}
}
如有錯誤之處,還望指出,定會及時改正。