你們好,我是阿濠,今篇內容跟你們分享的是數據結構之堆,很高興分享到segmentfault與你們一塊兒學習交流,初次見面請你們多多關照,一塊兒學習進步.
堆是一種特殊的樹
只要知足如下兩個條件,就能夠稱這顆樹爲堆node
1.堆是一顆徹底二叉樹算法
2.每一個節點必須(大於等於)或者(小於等於)其子樹中每一個節點的值json
二叉堆本質上是一種徹底二叉樹segmentfault
大頂堆
什麼是最大堆呢?
最大堆任何一個父節點的值,都大於等於它左右孩子節點的值。api
小頂堆
什麼是最小堆呢?
最小堆任何一個父節點的值,都小於等於它左右孩子節點的值。數組
二叉堆的根節點叫作堆頂
最大堆和最小堆的特色決定了:
在最大堆的堆頂是整個堆中的最大元素;在最小堆的堆頂是整個堆中的最小元素。數據結構
堆是一顆徹底二叉樹:
1.比較適合用數組存放,由於用數組存放不須要存儲左右子節點的指針。
2.很是節省空間,經過下標就能夠找到一個節點的左右子節點和父節點。
3.下標i的節點的左子節點的下標是i*2,右子節點的下標爲i*2+1,父節點的下標爲i/2
這是根節點存放在下標1的位置時的規律。dom
把一個無序的徹底二叉樹調整爲二叉堆,本質上就是讓全部非葉子節點依次下沉。學習
咱們從最後一個非葉子節點10開始。若是大於它左右孩子中最小的一個,則節點交換。測試
接下來輪到節點3,若是節點3大於它左右孩子中最小的一個,則節點3下沉。
接下來輪到節點1,若是節點1大於它左右孩子中最小的一個,則節點1下沉。但事實上節點1小於它的左右孩子,因此不用改變。
接下來輪到節點7,若是節點7大於它左右孩子中最小的一個,則節點7下沉。
節點7繼續比較,繼續下沉。
這樣一來,一顆無序的徹底二叉樹就構建成了一個最小堆。1就是最小的
咱們對堆作插入刪除或操做,可能會破壞堆的兩大特性,咱們就須要進行調整,使其從新知足堆的兩大特性,這個過程,咱們稱之爲堆化(heapify)
從下往上堆化和從上往下堆化。
往堆中插入一個元素
,咱們能夠用從下往上堆化
從下開始順着父節點路徑往上走,進行比較,而後交換。
往堆中插入元素22,以下圖:
往堆中刪除堆頂元素
,咱們能夠採用從上往下堆化
爲了不刪除後,堆是不徹底二叉樹,咱們將堆頂元素刪除後,要將最後一個元素放到堆頂,而後在進行堆化
例如:
咱們想刪除堆頂元素33,就須要刪除33後,將最後一個元素放在堆頂,而後進行堆化。
在擼代碼以前,咱們還須要明確一點:
二叉堆雖然是一顆徹底二叉樹,但它的存儲方式並非鏈式存儲,而是順序存儲。換句話說,二叉堆的全部節點都存儲在數組當中。
咱們準備將上面的例子中的樹這樣存儲:
[1,3,2,6,5,7,8,9,10]
/** * Java數組實現堆結構 * 下標格式以下 * 0 * 1 2 * 3 4 5 6 */ public class MyHeap { // find the index of left children // 下標爲i的節點的左側子節點的下標 public static int left(int i) { return (i + 1) * 2 - 1; } // find the index of right children // 下標爲i的節點的右側子節點的下標 public static int right(int i) { return (i + 1) * 2; } // find the index of parent // 下標爲i的節點的父節點的下標 public static int parent(int i) { return (i - 1) / 2; } // build the max heap // 構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } } public static void keepMaxHeap(int[] array, int k, int length) { int l = left(k);// k節點的左子節點 int r = right(k);// k節點的右子節點 int largest = k;// 假定k節點是k-l-r中的最大值節點 if(l<length && array[l]>array[largest]){// 左子節點>最大值節點 largest = l; } if(r<length && array[r]>array[largest]){// 右子節點>最大值節點 largest = r; } if(largest != k){// 發現最大值節點不是k節點時 是左或右子節點 交換 swap(array,k,largest); //int temp = array[k]; //array[k]= array[largest];// 新k節點爲最大值節點 //array[largest] = temp;// 左或右子節點爲原k節點值 // 迭代左或右子節點上的原k節點值 // 此時這個節點往下判斷左右子節點 keepMaxHeap(array,largest,length); } } // heap sort the array public static void heapSort(int[] array){ int length = array.length; for(int i=1;i<=length;i++){ swap(array,length-i,0); buildMaxHeap(array, length-i); } } // swap the value of index i and j in the array public static void swap(int[] array,int i,int j){ int temp = array[i]; array[i]= array[j]; array[j] = temp; } // print the array public static void printArray(int[] array){ for(int i : array){ System.out.print(i+" "); } System.out.println(""); } }
//堆排序的Java實現 做者 yangdau public class HeapTest { public static void main(String[] args) { int[] array = {1,3,2,6,5,7,8,9,10}; // 從小到大排序使用大頂堆 MyHeap.buildMaxHeap(array,array.length); System.out.println("生成大頂堆後的數據是: "); //輸出數組 MyHeap.printArray(array); MyHeap.heapSort(array); System.out.println("使用堆排序後的數據是: "); //輸出數組 MyHeap.printArray(array); } }
// 構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 for(int i=parent(length-1);i>=0;i--){ //傳入length=9,parent返回(9-1-1/2)=3 keepMaxHeap(array,i,length); } }
將從數組6開始,它的下標是3
public static void keepMaxHeap(int[] array, int k, int length) { int l = left(k);// k節點的左子節點 int r = right(k);// k節點的右子節點 int largest = k;// 假定k節點是k-l-r中的最大值節點 if(l<length && array[l]>array[largest]){// 左子節點>最大值節點 largest = l; } if(r<length && array[r]>array[largest]){// 右子節點>最大值節點 largest = r; } if(largest != k){// 發現最大值節點不是k節點時 是左或右子節點 交換 swap(array,k,largest); //int temp = array[k]; //array[k]= array[largest];// 新k節點爲最大值節點 //array[largest] = temp;// 左或右子節點爲原k節點值 // 迭代左或右子節點上的原k節點值 // 此時這個節點往下判斷左右子節點 keepMaxHeap(array,largest,length); } }
數組6的左節點l下標7,右節點r下標8,假設最大值就是6自己k下標3
進行判斷l的下標小於長度9而且9大於6,則最大值的值爲l
進行判斷r的下標小於長度9而且10的值大於9,則最大值的值爲r
進行判斷最大值的下標k不是3,右節點交換,進行再肯定最大值
這時再進行肯定最大值,此時最大值k的下標是8,它的左節點l下標是17,右節點r下標是18,進行判斷都不成立,自己是最大值,因此數組則i--=2,它的下標2
獲取2的左節點l下標5,右節點r下標6,假設最大值就是2自己k下標2,
進行判斷l的下標小於長度9而且7大於k,則最大值的值爲l
進行判斷r的下標小於長度9而且8的值大於7,則最大值的值爲r
進行判斷最大值的下標k不是2,右節點交換,進行肯定最大值
這時再進行肯定最大值,此時最大值k的下標是6,它的左節點l下標是14,右節點r下標是15,進行判斷都不成立,自己是最大值,因此數組則i--=1,它的下標1
獲取3的左節點l下標3,右節點r下標4,假設最大值就是3自己k下標1,
進行判斷l的下標小於長度而且10大於k,則最大值的值爲l
進行判斷r的下標小於長度而且5的值小於10,則最大值的值爲l
進行判斷最大值的下標k不是1,左節點交換,進行肯定最大值
這時再進行肯定最大值,此時最大值k的下標是3,它的左節點l下標是7,右節點r下標是8,進行判斷左節點小於長度9而且9大於3則最大值爲l,進行判斷r的下標小於長度而且6小於9,進行左節點交換則最大值爲l,判斷最大值下標不是自己3,再次確認最大值。
這時再進行肯定最大值,此時最大值k的下標是7,它的左節點l是15,右節點r是16,進行判斷都不成立,自己是最大值,因此數組則i--=0,它的下標0
獲取1的左節點10下標1,右節點r下標2,假設最大值就是1自己k下標0
進行判斷l的下標小於長度而且10大於k,則最大值的值爲l
進行判斷r的下標小於長度而且8的值小於10,則最大值的值爲l
進行判斷最大值的下標k不是0,左節點交換,進行肯定最大值
這時再進行肯定最大值,此時最大值k的下標是1,它的左節點l下標是3,右節點r下標是4,進行判斷左節點小於長度而且9大於1則最大值爲l,進行判斷r的下標小於長度而且5小於9,進行左節點交換則最大值爲l,判斷最大值下標不是自己1,再次確認最大值。
這時再進行肯定最大值,此時最大值k的下標是3,它的左節點l下標是7,右節點r下標是8,進行判斷左節點小於長度而且3大於1則最大值爲l,進行判斷r的下標小於長度而且6大於3,進行右節點交換則最大值爲r,判斷最大值下標不是自己3,再次確認最大值。
這時再進行肯定最大值,此時最大值k的下標是8,它的左節點l是17,右節點r是18,進行判斷都不成立,自己是最大值,因此數組結束
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組: for(int i=1;i<=length;i++){ //10 9 8 6 5 7 2 3 1 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-1的下標開始與0下標開始進行交換再進行大頂堆操做
// 構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=1,9,8,6,5,7,2,3,10 //length此時=8 //此時i=3 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// 構建某一數組的最大堆結構 public static void heapSort(int[] array){ int length = array.length; //數組:9,6,8,3,5,7,2,1,10 for(int i=1;i<=length;i++){ //i=2 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-2的下標開始與0下標開始進行交換再進行大頂堆操做
// 構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=1,6,8,3,5,7,2,9,10 //length此時=7 //此時i=2 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:8,6,7,3,5,1,2,9,10 for(int i=1;i<=length;i++){ //i=3 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-3的下標開始與0下標開始進行交換再進行大頂堆操做
構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=2,6,7,3,5,1,8,9,10 //length此時=6 //此時i=2 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:7,6,2,3,5,1,8,9,10 for(int i=1;i<=length;i++){ //i=4 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-4的下標開始與0下標開始進行交換再進行大頂堆操做
構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=1,6,2,3,5,7,8,9,10 //length此時=5 //此時i=1 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:6,5,2,3,1,7,8,9,10 for(int i=1;i<=length;i++){ //i=5 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-5的下標開始與0下標開始進行交換再進行大頂堆操做
構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=1,5,2,3,6,7,8,9,10 //length此時=4 //此時i=1 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:5,3,2,1,6,7,8,9,10 for(int i=1;i<=length;i++){ //i=6 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-6的下標開始與0下標開始進行交換再進行大頂堆操做
構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=1,3,2,5,6,7,8,9,10 //length此時=3 //此時i=1 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:3,1,2,5,6,7,8,9,10 for(int i=1;i<=length;i++){ //i=7 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-7的下標開始與0下標開始進行交換再進行大頂堆操做
構建某一數組的最大堆結構 public static void buildMaxHeap(int[] array,int length){ // the last node index is array.length-1 // and his parent index is (array.length-1-1)/2 //array此時=2,1,3,5,6,7,8,9,10 //length此時=2 //此時i=1 for(int i=parent(length-1);i>=0;i--){ keepMaxHeap(array,i,length); } }
// heap sort the array public static void heapSort(int[] array){ int length = array.length; //數組:2,1,3,5,6,7,8,9,10 for(int i=1;i<=length;i++){ //i=8 swap(array,length-i,0); buildMaxHeap(array, length-i); } }
從數組長度-8的下標開始與0下標開始進行交換再進行大頂堆操做
堆排序基本思想:
1.將待排序序列構形成一個大頂堆,此時整個序列的最大值就是堆頂的根節點。
2.將其與末尾元素進行交換,此時末尾就爲最大值。
3.而後將剩餘n-1個元素從新構形成一個堆,這樣會獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列
// heap sort the array public static void heapSort(int[] array){ int length = array.length; SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date time1=new Date(); String timeStr1=sim.format(time1); System.out.println("開始時間:"+timeStr1); for(int i=1;i<=length;i++){ swap(array,length-i,0); buildMaxHeap(array, length-i); } Date time=new Date(); String timeStr=sim.format(time); System.out.println("完成時間:"+timeStr); }
public static void main(String[] args) { int[] array =new int[800000]; for (int i=0;i<800000;i++){ array[i]=(int)(Math.random()*8000000); } System.out.println("排序前"); MyHeap.heapSort(array); // 從小到大排序使用大頂堆 System.out.println("排序後"); MyHeap.buildMaxHeap(array,array.length); MyHeap.heapSort(array); }
建堆的複雜度是O(n),將輸入數據依次插入空堆中,這須要n次連續插入操做。
排序的複雜是O(logn),咱們刪除堆頂元素,堆化,第二小的大堆頂了,再刪除,再堆化,...這些刪除的元素是否是正好有序的?
咱們直接把堆頂的元素與第n個元素交換位置,把(n-1)個元素堆化,再把堆頂元素與第(n-1)個元素交換位置,這時把(n-2)個元素堆化......進行下去,最後,數組中的元素就整個變成倒序的了,也就排序完了。
咱們知道刪除一個元素的時間複雜度是O(log n),那麼刪除n個元素正好是:O(n log n)
因爲堆排序過程當中存在堆頂元素和最後一個節點互換的操做,有可能改變原始相對順序,因此堆排序不是穩定的排序算法