堆排序
這里的堆并不是JVM中堆棧的堆,而是一種特殊的二叉樹,通常也叫作二叉堆。它具有以下特點:
- 它是完全二叉樹
- 堆中某個結點的值總是不大于或不小于其父結點的值
知識補充
二叉樹
樹中節(jié)點的子節(jié)點不超過2的有序樹
滿二叉樹
二叉樹中除了葉子節(jié)點,每個節(jié)點的子節(jié)點都為2,則此二叉樹為滿二叉樹。
完全二叉樹
如果對滿二叉樹的結點進行編號,約定編號從根結點起,自上而下,自左而右。則深度為k的,有n個結點的二叉樹,當且僅當其每一個結點都與深度為k的滿二叉樹中編號從1至n的結點一一對應時,稱之為完全二叉樹。
特點:葉子結點只能出現(xiàn)在最下層和次下層,且最下層的葉子結點集中在樹的左部。需要注意的是,滿二叉樹肯定是完全二叉樹,而完全二叉樹不一定是滿二叉樹。
二叉堆
二叉堆是一種特殊的堆,可以被看做一棵完全二叉樹的數(shù)組對象,而根據(jù)其性質又可以分為下面兩種:
- 大根堆:每一個根節(jié)點都大于等于它的左右孩子節(jié)點,也叫最大堆
- 小根堆:每一個根節(jié)點都小于等于它的左右孩子節(jié)點,也叫最小堆
如果把一個數(shù)組通過大根堆的方式來表示(數(shù)組元素的值是可變的),如下:
由此可以推出:
- 對于位置為 k 的節(jié)點,其子節(jié)點的位置分別為,左子節(jié)點 = 2k + 1,右子節(jié)點 = 2(k + 1)
如:對于 k = 1,其節(jié)點的對應數(shù)組為 5
左子節(jié)點的位置為 3,對應數(shù)組的值為 3
右子節(jié)點的位置為 4,對應數(shù)組的值為 2
- 最后一個非葉子節(jié)點的位置為 (n/2) - 1,n為數(shù)組長度
如:數(shù)組長度為6,則 (6/2) - 1 = 2,即位置 2 為最后一個非葉子節(jié)點
給定一個隨機數(shù)組[35,63,48,9,86,24,53,11],
將該數(shù)組視為一個完全二叉樹:
從上圖很明顯的可以看出,這個二叉樹不符合大根堆的定義,但是可以通過調整,使它變?yōu)樽畲蠖选H绻?strong>從最后一個非葉子節(jié)點開始,從下到上,從右往左調整,則:
通過上面的調整,該二叉樹為最大堆,這個時候開始排序,排序規(guī)則:
- 將堆頂元素和尾元素交換交換
- 后重新調整元素的位置,使之重新變成二叉堆
代碼實現(xiàn)
public class HeapSort { public static final int[] ARRAY = {35, 63, 48, 9, 86, 24, 53, 11}; public static int[] sort(int[] array) { //數(shù)組的長度 int length = array.length; if (length < 2) return array; //首先構建一個最大堆 buildMaxHeap(array); //調整為最大堆之后,頂元素為最大元素并與微元素交換 while (length > 0) {//當lenth <= 0時,說明已經(jīng)到堆頂 //交換 swap(array, 0, length - 1); length--;//交換之后相當于把樹中的最大值彈出去了,所以要-- //交換之后從上往下調整使之成為最大堆 adjustHeap(array, 0, length); } return array; } //對元素組構建為一個對應數(shù)組的最大堆 private static void buildMaxHeap(int[] array) { //在之前的分析可知,最大堆的構建是從最后一個非葉子節(jié)點開始,從下往上,從右往左調整 //最后一個非葉子節(jié)點的位置為:array.length/2 - 1 for (int i = array.length / 2 - 1; i >= 0; i--) { //調整使之成為最大堆 adjustHeap(array, i, array.length); } } /** * 調整 * @param parent 最后一個非葉子節(jié)點 * @param length 數(shù)組的長度 */ private static void adjustHeap(int[] array, int parent, int length) { //定義最大值的索引 int maxIndex = parent; //parent為對應元素的位置(數(shù)組的索引) int left = 2 * parent + 1;//左子節(jié)點對應元素的位置 int right = 2 * (parent + 1);//右子節(jié)點對應元素的位置 //判斷是否有子節(jié)點,再比較父節(jié)點和左右子節(jié)點的大小 //因為parent最后一個非葉子節(jié)點,所以如果有左右子節(jié)點則節(jié)點的位置都小于數(shù)組的長度 if (left < length && array[left] > array[maxIndex]) {//左子節(jié)點如果比父節(jié)點大 maxIndex = left; } if (right < length && array[right] > array[maxIndex]) {//右子節(jié)點如果比父節(jié)點大 maxIndex = right; } //maxIndex為父節(jié)點,若發(fā)生改變則說明不是最大節(jié)點,需要交換 if (maxIndex != parent) { swap(array, maxIndex, parent); //交換之后遞歸再次調整比較 adjustHeap(array, maxIndex, length); } } //交換 private static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void print(int[] array) { for (int i : array) { System.out.print(i + " "); } System.out.println(""); } public static void main(String[] args) { print(ARRAY); System.out.println("============================================"); print(sort(ARRAY)); } }
時間復雜度
堆的時間復雜度是 O(nlogn)
參考:堆排序的時間復雜度分析
算法穩(wěn)定性
堆的結構為,對于位置為 k 的節(jié)點,其子節(jié)點的位置分別為,左子節(jié)點 = 2k + 1,右子節(jié)點 = 2(k + 1),最大堆要求父節(jié)點大于等于其2個子節(jié)點,最小堆要求父節(jié)點小于等于其2個子節(jié)點。
在一個長為n的序列,堆排序的過程是從第n/2開始和其子節(jié)點共3個值選擇最大(最大堆)或者最小(最大堆),這3個元素之間的選擇當然不會破壞穩(wěn)定性。但當為n/2-1,n/2-2,… 1 這些個父節(jié)點選擇元素時,就會破壞穩(wěn)定性。有可能第n/2個父節(jié)點交換把后面一個元素交換過去了,而第n/2-1個父節(jié)點把后面一個相同的元素沒有交換,那么這2個相同的元素之間的穩(wěn)定性就被破壞了。所以,堆排序不是穩(wěn)定的排序算法。
思考
對于快速排序來說,其平均復雜度為O(nlogn),堆排序也是O(nlogn),怎么選擇?如下題:
leetcode:數(shù)組中的第K個最大元素
此題的意思是對于一個無序數(shù)組,經(jīng)過排序后的第 k 個最大的元素。
我們知道快速排序是需要對整個數(shù)組進行排序,這樣才能取出第 k 個最大的元素。
如果使用堆排序,且是最大堆的方式,則第k次循環(huán)即可找出第 k 個最大的元素,并不需要吧整個數(shù)組排序。
所以對于怎么選擇的問題,要看具體的場景,或者是兩者都可。
總結
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關注服務器之家的更多內(nèi)容!
原文鏈接:https://blog.csdn.net/weixin_43477531/article/details/119821587