堆排序
-
- 1、初識堆排序
- 2、堆排序代碼實作
- 3、堆排序總結
1、初識堆排序
二叉堆的特性:
- 最大堆的堆頂是整個堆中的最大元素
- 最小堆的堆頂是整個堆中的最小元素
以最大堆為例,如果删除一個最大堆的堆頂(并不是完全删除,而是跟末尾的點交換位置),經過自我調整,第2大的元素就會被交換上來,成為最大堆的新堆頂,如下圖:

如上圖所示,在删除值為10的堆頂節點後,經過調整,值為9的新節點就會頂替上來;在删除值為9的堆頂節點後,經過調整,值為8的新節點就會頂替上來……
由于二叉堆的這個特性,每一次删除舊堆頂,調整後的新堆頂都是大小僅次于舊堆頂的節點。那麼隻要反複删除堆頂,反複調整二叉堆,所得到的集合就會成為 一個有序集合,過程如下:
- 删除節點9,節點8成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點8,節點7成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點7,節點6成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點6,節點5成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點5,節點4成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點4,節點3成為新堆頂
圖解排序算法(04) -- 堆排序 - 删除節點3,節點2成為新堆頂
圖解排序算法(04) -- 堆排序 經過上述步驟,原本的最大二叉堆已經變成了一個從小到大的有序集合。
二叉堆實際存儲在數組中,數組中的元素排列如下:
由此,歸納出堆排序算法的步驟:圖解排序算法(04) -- 堆排序
- 把無序數組建構成二叉堆。需要從小到大排序,則建構成最大堆;需要從大到小排序,則建構成最小堆。
- 循環删除堆頂元素,替換到二叉堆的末尾,調整堆産生新的堆頂。
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)。
———————————————————————————————————————
内容來源:《漫畫算法》
關注公衆号,回複 【算法】,擷取高清算法書!