數據結構 - 堆

二叉堆

二叉堆是一顆二叉樹,不必定是滿二叉樹,但必定是徹底二叉樹,徹底二叉樹指的是容許有缺失的部分,但缺失節點的部分必定是右下側。api

堆中任意一個節點的值必定大於等於他的孩子節點。這個特色也說明了,根節點的元素是最大的元素。這樣的堆叫作最大堆,相反若是任意一個節點小於等於他的孩子節點,那就是最小堆。這裏要注意:不是越貼近根節點的值就越大。數組

因爲最大堆是徹底二叉樹,那麼最大堆就能夠用數組來做爲底層實現。如圖所示,編號1-10就表明一個數組的索引,或者用0-9也OK。
image.png數據結構

若是用1-10索引來表示的話,好比41這個元素,索引爲2,他的左孩子索引就是4(i x 2) ,右孩子節點就是5 (i x 2 + 1)。
相反,知道左孩子或者右孩子,求父節點直接除2就能夠了,4 / 2 ,5 / 2強轉爲int以後都是2。
若是用0-9索引表示這些元素,已知父親節點索引爲1,左孩子節點就是4( i x 2 + 1),右孩子節點是5 (i x 2 + 2),知道左孩子或右孩子索引3和4,父節點索引就是1(i - 1)/ 2。性能

知道了這些特色,開始用代碼實現一個最大堆spa

//使用數組看成底層數據結構
public MyArray<E> myArray;
 
public MaxHeap(int capacity){
    myArray = new MyArray<>(capacity);
}

public MaxHeap(){
    myArray = new MyArray<>();
}

//父節點的索引 (i-1)/2
public int parent(int index){
    if(index == 0)
        throw new IllegalArgumentException("index:0,doesn't hava parent.");
    return (index - 1) / 2;
}

//左孩子節點 i\*2+1
public int leftChild(int index){
    return index \* 2 + 1;
}

//右孩子節點 i\*2+2
public int rightChild(int index){
    return index \* 2 + 2;
}

添加一個節點

image.png

添加節點的時候,要知足最大堆的性質須要聽從兩點,第一就是要添加到數組末尾的位置,知足徹底二叉樹的特性。第二就是知足每個節點要比他的孩子節點要大的特性,52添加到末尾後,要不斷的向上尋找父節點,和父節點比較大小後,和父節點元素作交換操做,找到本身合適的位置,咱們把這個操做稱爲ShiftUp(上浮)code

public void add(E e){
    myArray.addLast(e);
    shiftUp(myArray.getSize() - 1);
}

private void shiftUp(int index){
    //把當前index的值和父節點的值作比較,小於父節點就向上移動
    while(index > 0 && myArray.getByIndex(index).compareTo(myArray.getByIndex(parent(index))) > 0)
    {
        //對兩個索引交換
        myArray.swap(index,parent(index));
        
        //從新標記index位置,用於下次交換邏輯
        index = parent(index);
    }
}

取出根元素

image.png
若是想取出根元素62,取出後索引爲0的位置就爲空了,爲了知足最大堆的結構,就須要從新組織這顆二叉樹。咱們採用將最後一個元素放到根元素的位置,而後再根據這個元素,不斷向下去判斷本身是否是比兩個孩子節點都大,若是大就不須要移動。若是比孩子節點小,就向下挪動,由於有兩個孩子,還須要比較這兩個孩子誰更大,誰大就和誰交換。這個過程叫作shift down(下沉)。
image.pngblog

//取出最大的元素
public E extractMax()
{
    E max = findMax();
    myArray.swap(0,myArray.getSize() - 1);
    myArray.removeLast();
    shiftDown(0);
    return max;
}
//查看一下最大元素是誰
public E findMax()
{
    if(myArray.getSize() == 0)
        throw new IndexOutOfBoundsException("heap is empty.");
    return myArray.getByIndex(0);
}
private void shiftDown(int index) {
    //若是一個元素的左孩子大於了總數量,那麼就結束
    while (leftChild(index) < myArray.getSize()) {
        //判斷有右孩子的狀況下,在與左孩子作比較
        int swapIndex = leftChild(index); //默認是左孩子大
        ////若是有右孩子而且右孩子比左孩子大
        if (swapIndex + 1 < myArray.getSize() &&
                myArray.getByIndex(swapIndex).compareTo(myArray.getByIndex(swapIndex + 1)) < 0) {
            swapIndex = rightChild(index);
        }
        //堆的性質:只要當前的父親節點大於兩個孩子節點便可,不須要一直往下看,直接結束循環
        if (myArray.getByIndex(index).compareTo(myArray.getByIndex(swapIndex)) > 0) {
            break;
        }
        myArray.swap(index, swapIndex);//和子節點交換元素
        index = swapIndex;//從新標記index的位置,便於下次循環邏輯
    }
}

Replace操做

replace就是替換根元素的操做,簡單的思路能夠是先取出根元素,也就是上面實現的extractMax()方法,而後在添加一個元素。但這樣至關於兩個O(logn)的操做。還有一種實現思路就是直接將根元素替換掉,而後作一個shift down就解決了。索引

//取出根元素,並替換成最新元素
public E replace(E e) {
    E e1 = findMax();
    myArray.set(0,e);
    shiftDown(0);
    return e1;
}

Heapify操做

heapify是將一個數組轉換爲一個堆的過程,這個實現徹底能夠用添加操做來完成,可是複雜度是O(nlogn)級別的,若是將一個數組一次性的放到堆中,找到最後一個非葉子節點,從這個節點向前遍歷,將每個元素作一個下沉操做就能夠了。那如何定位最後一個非葉子節點呢?很簡單就是最後一個葉子節點的父節點。
image.pngci

public MaxHeap(E[] arr){
    //將一個數組直接轉換爲最大堆
    myArray = new MyArray<>(arr);
    //parent(arr.getSize - 1)是除了葉子節點之外,開始向上遍歷
    for(int x = parent(myArray.getSize() - 1); x >= 0 ; x--) {
        shiftDown(x);
    }
}

D叉堆

二叉堆是一個節點有兩個兒子,D叉堆就是一個節點有D個兒子,這樣樹的深度就會變短。用法和二叉堆差很少,只不過元素在上浮和下沉的時候須要多判斷一下。至於D取值爲多少性能最好,仍是要實戰的試一下的。
image.pngrem

相關文章
相關標籤/搜索