我知道的數據結構之堆

做者前言

你們好,我是阿濠,今篇內容跟你們分享的是數據結構之堆,很高興分享到segmentfault與你們一塊兒學習交流,初次見面請你們多多關照,一塊兒學習進步.

1、什麼是堆

堆是一種特殊的樹

只要知足如下兩個條件,就能夠稱這顆樹爲堆node

1.堆是一顆徹底二叉樹算法

2.每一個節點必須(大於等於)或者(小於等於)其子樹中每一個節點的值json

clipboard.png

二叉堆本質上是一種徹底二叉樹segmentfault

分爲兩個類型:大頂堆與小頂堆

大頂堆

什麼是最大堆呢?
最大堆任何一個父節點的值,都大於等於它左右孩子節點的值。api

image

小頂堆

什麼是最小堆呢?
最小堆任何一個父節點的值,都小於等於它左右孩子節點的值。數組

image

二叉堆的根節點叫作堆頂

最大堆和最小堆的特色決定了:
在最大堆的堆頂是整個堆中的最大元素;在最小堆的堆頂是整個堆中的最小元素數據結構

2、如何構建堆?

image

堆是一顆徹底二叉樹:
1.比較適合用數組存放,由於用數組存放不須要存儲左右子節點的指針。
2.很是節省空間,經過下標就能夠找到一個節點的左右子節點和父節點。
3.下標i的節點的左子節點的下標是i*2,右子節點的下標爲i*2+1,父節點的下標爲i/2
這是根節點存放在下標1的位置時的規律。dom

clipboard.png

把一個無序的徹底二叉樹調整爲二叉堆,本質上就是讓全部非葉子節點依次下沉學習

舉一個無序徹底二叉樹的例子


咱們從最後一個非葉子節點10開始。若是大於它左右孩子中最小的一個,則節點交換。測試

image

接下來輪到節點3,若是節點3大於它左右孩子中最小的一個,則節點3下沉。

image

接下來輪到節點1,若是節點1大於它左右孩子中最小的一個,則節點1下沉。但事實上節點1小於它的左右孩子,因此不用改變。

接下來輪到節點7,若是節點7大於它左右孩子中最小的一個,則節點7下沉。

image

節點7繼續比較,繼續下沉。

image

這樣一來,一顆無序的徹底二叉樹就構建成了一個最小堆。1就是最小的

3、堆的插入和刪除

咱們對堆作插入刪除或操做,可能會破壞堆的兩大特性,咱們就須要進行調整,使其從新知足堆的兩大特性,這個過程,咱們稱之爲堆化(heapify)

堆化有兩種:從下往上堆化和從上往下堆化。

往堆中插入一個元素,咱們能夠用從下往上堆化

從下開始順着父節點路徑往上走,進行比較,而後交換。

往堆中插入元素22,以下圖:

clipboard.png

往堆中刪除堆頂元素,咱們能夠採用從上往下堆化

爲了不刪除後,堆是不徹底二叉樹,咱們將堆頂元素刪除後,要將最後一個元素放到堆頂,而後在進行堆化

例如:

咱們想刪除堆頂元素33,就須要刪除33後,將最後一個元素放在堆頂,而後進行堆化。

clipboard.png

4、代碼實現

image

在擼代碼以前,咱們還須要明確一點:

二叉堆雖然是一顆徹底二叉樹,但它的存儲方式並非鏈式存儲,而是順序存儲。換句話說,二叉堆的全部節點都存儲在數組當中。

640?wx_fmt=png

咱們準備將上面的例子中的樹這樣存儲:

[1,3,2,6,5,7,8,9,10]

640?wx_fmt=jpeg

/**
 * 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);
    }
}

圖片.png

// 構建某一數組的最大堆結構
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);
    }
}
  1. 進行判斷若是l的下標小於長度而且該下標的值大於k,則最大值的值爲l
  2. 進行判斷若是r的下標小於長度而且該下標的值大於k,則最大值的值爲r
  3. 進行判斷若是最大值的下標k不是自己k時,進行左或右節點交換,進行肯定最大值

數組6的左節點l下標7,右節點r下標8,假設最大值就是6自己k下標3

進行判斷l的下標小於長度9而且9大於6,則最大值的值爲l
進行判斷r的下標小於長度9而且10的值大於9,則最大值的值爲r
進行判斷最大值的下標k不是3,右節點交換,進行再肯定最大值

圖片.png

這時再進行肯定最大值,此時最大值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,右節點交換,進行肯定最大值

圖片.png

這時再進行肯定最大值,此時最大值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,左節點交換,進行肯定最大值

圖片.png

這時再進行肯定最大值,此時最大值k的下標是3,它的左節點l下標是7,右節點r下標是8,進行判斷左節點小於長度9而且9大於3則最大值爲l,進行判斷r的下標小於長度而且6小於9,進行左節點交換則最大值爲l,判斷最大值下標不是自己3,再次確認最大值。

圖片.png

這時再進行肯定最大值,此時最大值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,左節點交換,進行肯定最大值

圖片.png

這時再進行肯定最大值,此時最大值k的下標是1,它的左節點l下標是3,右節點r下標是4,進行判斷左節點小於長度而且9大於1則最大值爲l,進行判斷r的下標小於長度而且5小於9,進行左節點交換則最大值爲l,判斷最大值下標不是自己1,再次確認最大值。

圖片.png

這時再進行肯定最大值,此時最大值k的下標是3,它的左節點l下標是7,右節點r下標是8,進行判斷左節點小於長度而且3大於1則最大值爲l,進行判斷r的下標小於長度而且6大於3,進行右節點交換則最大值爲r,判斷最大值下標不是自己3,再次確認最大值。

圖片.png

這時再進行肯定最大值,此時最大值k的下標是8,它的左節點l是17,右節點r是18,進行判斷都不成立,自己是最大值,因此數組結束

圖片.png
123.gif

圖片.png
640?wx_fmt=jpeg

// 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下標開始進行交換再進行大頂堆操做

圖片.png

// 構建某一數組的最大堆結構
    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);
        }
    }

2020-02-20_171912.png

// 構建某一數組的最大堆結構
    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下標開始進行交換再進行大頂堆操做

圖片.png

// 構建某一數組的最大堆結構
    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);
        }
    }

2020-02-20_173453.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

構建某一數組的最大堆結構
    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);
        }
    }

2020-02-20_174803.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

構建某一數組的最大堆結構
    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);
        }
    }

2020-02-20_184655.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

構建某一數組的最大堆結構
    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);
        }
    }

2020-02-20_200603.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

構建某一數組的最大堆結構
    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);
        }
    }

圖片.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

構建某一數組的最大堆結構
    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);
        }
    }

圖片.png

// 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下標開始進行交換再進行大頂堆操做

圖片.png

堆排序基本思想:
1.將待排序序列構形成一個大頂堆,此時整個序列的最大值就是堆頂的根節點。
2.將其與末尾元素進行交換,此時末尾就爲最大值。
3.而後將剩餘n-1個元素從新構形成一個堆,這樣會獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列

圖片.png

堆排序的速度測試

// 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);  
}

圖片.png

5、堆的時間程度

建堆的複雜度是O(n),將輸入數據依次插入空堆中,這須要n次連續插入操做。

排序的複雜是O(logn),咱們刪除堆頂元素,堆化,第二小的大堆頂了,再刪除,再堆化,...這些刪除的元素是否是正好有序的?

咱們直接把堆頂的元素與第n個元素交換位置,把(n-1)個元素堆化,再把堆頂元素與第(n-1)個元素交換位置,這時把(n-2)個元素堆化......進行下去,最後,數組中的元素就整個變成倒序的了,也就排序完了。
123.gif

咱們知道刪除一個元素的時間複雜度是O(log n),那麼刪除n個元素正好是:O(n log n)

因爲堆排序過程當中存在堆頂元素和最後一個節點互換的操做,有可能改變原始相對順序,因此堆排序不是穩定的排序算法

相關文章
相關標籤/搜索