原創不易,如果你覺得對你有用,哪怕一點點也好,請留下一個贊再走,謝謝啦啦啦!!
一、前期知識儲備
- 堆排序是對於完全二叉樹而言
完全二叉樹的那些事:
- 定義:對於一個樹高爲h的二叉樹,如果其第0層至第h-1層的節點都滿。如果最下面一層節點不滿,則所有的節點在左邊的連續排列,空位都在右邊。這樣的二叉樹就是一棵完全二叉樹(不理解沒關係,是我的錯,不許噴我)
- 性質:如果n個節點的完全二叉樹的節點按照層次並按從左到右的順序從0開始編號,對於每個節點都有:
- 序號爲0的節點是根對於i>0,
- 其父節點的編號爲(i-1)/2。
- 若2·i+1<n,其左子節點的序號爲2·i+1,否則沒有左子節點
- 若2·i+2<n,其右子節點的序號爲2·i+2,否則沒有右子節點。
是不是很枯燥?是不是看不下去?小夥子,彆着急,磨刀不誤砍柴工。,我給你上個圖,你就懂上面所說的一切了。
2. 那堆又是什麼呢?
- 堆的定義:
- 每個非葉子結點的值都大於或等於其左孩子和右孩子結點的值,稱之爲大根堆
- 每個結點的值都小於或等於其左孩子和右孩子結點的值,稱之爲小根堆
- 大根堆常用於升序操作
- 小根堆常用於降序操作
千言萬語不勝一圖,看圖哈
二、堆如何跟排序掛上鉤的?
理由很簡單,因爲堆這種數據結構可以用於排序(雖然感覺是廢話,哈哈哈哈,不管了,先說人話)不信你往下看。
- 根據大頂堆的特性,我們可以知道大根堆的根節點就是所有節點的最大值
- 所以我們可以將最大值的根節點取下(人話:替換)與最後一個節點的值進行互換,然後將剩餘的節點繼續構造成大根堆
三、堆排序思想
非常重要呀呀呀,不懂也沒關係,下面有圖有真相
-
首先將待排序的數組構造成一個大根堆,此時,整個數組的最大值就是堆結構的頂端(根節點)
-
將頂端的數與末尾的數交換,此時,末尾的數爲最大值,剩餘待排序數組個數爲n-1
-
將剩餘的n-1個數再構造成大根堆,再將頂端數與n-1位置的數交換,如此反覆執行,便能得到有序數組
四、如何進行堆排序(附親手畫的圖)
上面說的是不是還是很懵逼???,沒關係,既然是說人話,那我就上個圖來解釋:
說明:以下畫的樹必須滿足完全二叉樹,因爲這是堆這個數據結構的前提
- 給定一個數組,如何知道非哪些元素是非葉子節點個數呢?
- 根據一條公式即可知道了,
total = arry.length-1
(下標從0開始)- 看下圖哈
- 構建大頂堆順序
- 依次處理非葉子節點
- 順序是從上往下、從右往左
- 堆排序
簡要說一下人話:
- 給定一個待排序的數組,我們要將它變成大頂堆數組,這裏之所以要給出完全二叉樹來,是因爲這樣便於理解哈
- 我們是直接操作數組使其變成大頂堆,而不是樹,但是原理是一樣的,因爲我們操作數組的時候就是用到上面說的完全二叉樹的性質(下標索引找孩子節點)
- 這裏用樹來演示,就是爲了便於理解,真正操作是對數組直接操作。
再放兩個圖:
仔細品,不懂可以留言交流呀!圖醜也歡迎留言噴哈哈哈
調整好大頂堆後,根(arry[0])節點值跟倒二節點(arry[5])值又互換,看圖
五、擼代碼(詳細註釋,不怕你看不懂)
紙上得來終且淺,得知此時要躬行,看了再多還是得擼代碼
如果你看到這裏了,要個贊不過分吧,哈哈哈哈
代碼:
package suanFa;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* 堆排序的學習:
* 時間複雜度:O(nlogn),遠比冒泡,簡單選擇,直接插入的O(n^2)好很多,但是它是個不穩定的算法
* @公衆號 放牛娃學編程
*
*/
public class HeapSort {
public static void main(String[] args) {
// int[] arry = {20};
// //測試一波
// Heapsort(arry);
// System.out.println("數組已經排序完了:"+Arrays.toString(arry));
//性能測試一波,8千萬數據排序
int[] arry = new int[80000000];
for(int i = 0; i < 80000000; i++)
{
arry[i] = (int) (Math.random()*80000000);
}
Date start = new Date();
Heapsort(arry);
Date end = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startTime = simpleDateFormat.format(start);
String endTime = simpleDateFormat.format(end);
System.out.println("排序前的時間:"+startTime);
System.out.println("排序後的時間:"+endTime);
}
//構建大頂堆
public static void Heapsort(int[] arry)
{
//構建一棵大頂堆,策略:依次處理非葉子節點,從右往左,從上往下
for(int i = arry.length / 2 - 1; i >= 0; i--)
{
HeapAdjust(arry, i, arry.length);
}
//開始排序(取大頂堆的根節點,即使最大值,跟數組最後一個調換位置,然後將剩餘的數重新調整爲大頂堆)
for(int j = arry.length - 1; j > 0 ; j--)
{
//交換位置
swap(arry,0, j);
//將剩餘的節點(這裏是數組)調整爲大頂堆
HeapAdjust(arry, 0, j);
}
}
private static void swap(int[] arry, int i, int j) {
// TODO Auto-generated method stub
int temp = arry[i];
arry[i] = arry[j];
arry[j] = temp;
}
//調整堆
/**
*
* @param arry 需要調整的數組
* @param index 非葉子節點下標索引
* @param length 需要調整的數組長度
*/
public static void HeapAdjust(int[] arry, int index, int length)
{
//1.0 用一個臨時變量將該節點值暫時保存起來
int temp = arry[index];
int k;
//下標爲什麼要這樣變化,根據完全二叉樹的父節點與子節點的關係得出來的(編號從0開始編號)
for(k=2*index + 1; k < length; k = 2*k+1)
{
if(k+1 < length && arry[k] < arry[k+1])
{
//指向右子節點(較大的下標)
++k;
}
/**如果父節點已經是大於等於孩子節點(那直接退出循環,爲什麼可以這樣呢?
因爲我們採取的策略是:處理非葉子節點時,從右往左,從上往下。假設非葉子節點編號爲:4,3,2,1)
**/
if(temp >= arry[k])
{
break;
}
//替換
arry[index] = arry[k];
index = k;
}
//插入
arry[index] = temp;
}
}
六、 八千萬數據測試堆排序性能
上圖程序中,我用8千萬的數據進行堆排序,我的電腦用時:
- 可能我的電腦配置比較差勁,但是比冒泡、簡單排序好太多了,不信你自己玩玩,我這破電腦就不試了哈
- 歡迎留言看看你的運行時間是多少,看是不是吊打我電腦呢,哈哈哈哈哈
- 看在我熬夜肝圖的份上就點個贊吧,圖不好也可以噴哈哈哈
都已經看到這了,再不給贊,那就說不過去了吧
七、分享交流
最後有興趣一起交流的,可以關注我的公衆號:這裏你能夠學到很實用的技巧,不是常用的我不說,公衆號回覆提取碼即可獲取以下學習資料啦啦啦啦,喜歡就拿去吧!!
(鏈接時常會失效,若出現此類情況,可以加我微信:17722328325(加時請備註:學習資料))
-
Java web從入門到精通電子書
-
Python機器學習電子書
-
Python400集(北京尚學堂)
-
JavaScript項目案例、經典面試題
-
Java300集(入門、精通)
-
Java後端培訓機構錄集(同事培訓內部提供)
-
java重要知識pdf文檔(價值連城呀呀,不收藏你會後悔的)
額外一堆電子書:
喜歡就關注吧,點個贊吧
這是分享生活、電影、資料、書籍的一個公衆號,有需要的也可以看看喲!