數據結構之二叉堆

一、堆的定義

二叉堆是一種特殊的二叉樹,它的每個結點都大於等於其兩個子結點,這種被稱爲最大堆,當結點都小於其兩個子結點時,被稱爲最小堆。它的存儲結構一般都選擇使用數組而不是鏈表。
在這裏插入圖片描述

二、堆的實現

觀察上圖,其實二叉堆是一種特殊的二叉樹,這種特殊之處就在於二叉堆永遠都是一棵完全二叉樹,這個特性使得我們可以選擇使用數組來實現,具體方法就是將二叉樹上的元素按層級一級一級的放入數組中,如下圖所示。那麼在數組中,父結點和左右子結點的關係是如何的呢?
在這裏插入圖片描述
通過觀察可以發現:
父結點與子結點在數組中的座標分別滿足以下關係:

leftChild= parent *2 + 1;
rightChild= parent *2 + 2;
parent = (child-1)/2;

好了,有了這個認識我們就可以開始逐步去實現了,以最大堆爲例。對於最大堆,其存儲結構選擇另一篇博文中實現的動態數組,具體實現可以移步去閱讀一下。

public class MaxHeap<T extends Comparable<T>> {

    private Array<T> array;

    public MaxHeap(){
        this.array = new Array<>();
    }
}   

再定義出三個上面提到的父子結點之間關係的三個函數

public class MaxHeap<T extends Comparable<T>> {

    private Array<T> array;

    public MaxHeap(){
        this.array = new Array<>();
    }

    //父親節點
    private int(int child){
        if(child>0)
            return (child-1)/2;
        else
            throw new IllegalStateException(child + "don't have parent node.");
    }

    //左孩子節點
    private int leftChild(int parent){
        return parent*2+1;
    }

    //右孩子節點
    private int rightChild(int parent){
        return parent*2 + 2;
    }
}

有了這個基礎,就可以考慮怎麼往堆中添加元素了。對於添加元素,需要藉助一個上浮的操作。下面以圖的形式解釋什麼是上浮。以上面的最大堆爲例,假如現在添加一個元素7,那麼怎麼添加呢?其過程如下:

  1. 將待添加的元素7添加到堆尾,此時其結構如下:
    在這裏插入圖片描述
  2. 將新添加的元素進行上浮操作。所謂的上浮,就是將元素與其父元素對比,當發現其比父元素大時就交換,然後繼續與其父元素對比,知道其小於等於其父元素。在這裏,就將7與其父元素6對比,發現比6大,就交換,交換後如下圖
    在這裏插入圖片描述
  3. 然後7與其父元素10比較,發現比10小,停止上浮,添加元素完成。

所以整個添加元素的過程比較簡單,其代碼實現如下:

public class MaxHeap<T extends Comparable<T>> {

    private Array<T> array;

    public MaxHeap(){
        this.array = new Array<>();
    }

    //父親節點
    public int parent(int child){
        if(child>0)
            return (child-1)/2;
        else
            throw new IllegalStateException(child + "don't have parent node.");
    }

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

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

    /**
     * 堆中已添加的元素個數
     * @return
     */
    public int getSize(){
        return array.getSize();
    }

    /**
     * 往堆中添加元素
     * @param elem
     */
    public void add(T elem){
        //將元素添加到堆底
        array.addLast(elem);
        //進行上升操作
        shiftUp(array.getSize()-1);
    }

    /**
     * 堆中節點上升操作
     * @param index
     */
    private void shiftUp(int index){
        while(index > 0){
            int p = parent(index);
            if(array.get(p).compareTo(array.get(index))<0){ //父元素比子元素小
                array.swap(index, p);
                index = p;
            }else{
                break;
            }
        }
    }
}

講完了添加元素,下面來講一下從堆中刪除元素。對於堆中元素的刪除,默認是刪除堆頂的元素
刪除元素的過程如下:

  1. 首先,將堆頂元素與堆尾元素互換,然後刪除堆尾的元素,而堆尾的元素可以直接刪除,不需要其他的操作。
    在這裏插入圖片描述
    在這裏插入圖片描述
  2. 將堆頂的元素進行下沉操作。所謂的下沉,其過程其實也很簡單,首先比較當前元素的左右結點,選出其中較大的與父元素交換,這樣就保證了最大堆的性質。然後繼續迭代此操作,直到其大於等於其兩個子元素。
    在這裏插入圖片描述
    代碼實現如下:
public class MaxHeap<T extends Comparable<T>> {

    private Array<T> array;

    public MaxHeap(){
        this.array = new Array<>();
    }

    //父親節點
    public int parent(int child){
        if(child>0)
            return (child-1)/2;
        else
            throw new IllegalStateException(child + "don't have parent node.");
    }

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

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

    /**
     * 堆中已添加的元素個數
     * @return
     */
    public int getSize(){
        return array.getSize();
    }

    /**
     * 往堆中添加元素
     * @param elem
     */
    public void add(T elem){
        //將元素添加到堆底
        array.addLast(elem);
        //進行上升操作
        shiftUp(array.getSize()-1);
    }

    /**
     * 堆中節點上升操作
     * @param index
     */
    private void shiftUp(int index){
        while(index > 0){
            int p = parent(index);
            if(array.get(p).compareTo(array.get(index))<0){ //父元素比子元素小
                array.swap(index, p);
                index = p;
            }else{
                break;
            }
        }
    }

    /**
     * 刪除堆頂元素
     * @return
     */
    public T remove(){
        if(!array.isEmpty()){
            T res = array.get(0);
            array.swap(0, array.getSize()-1); //調換堆頂和堆底元素
            array.removeLast(); //刪除原來堆頂元素
            shiftDown(0); //將現在堆頂元素進行下降操作
            return res;
        }else{
            throw new IllegalStateException("MaxHeap is empty.");
        }
    }

    /**
     * 下降操作
     * @param index
     */
    private void shiftDown(int index){
        while(leftChild(index) < array.getSize()){
            int j = leftChild(index);
            if(j+1 < array.getSize() && array.get(j).compareTo(array.get(j+1))<0){ //說明有右孩子,且左孩子比有右孩子小
                j++;
            }
            //array.get(j)此時是最大值
            if(array.get(index).compareTo(array.get(j))>0){ //此時元素已到達正確位置
                break;
            }
            array.swap(j,index);
            index = j;
        }
    }
}

三、測試

public class TestMaxHeap {
	   public static void main(String[] args) {
        	MaxHeap<Integer> maxHeap = new MaxHeap<>();
        	Random random = new Random();
        	for(int i=0;i<10;i++){
            	maxHeap.add(random.nextInt(50));
        	}
        	for(int i=0;i<10;i++){
            	System.out.print(maxHeap.remove()+" ");
        	}
        	System.out.println();
    }
}

結果無誤:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章