數據結構:樹形結構之堆

慾望以提升熱忱,毅力以磨平高山。

什麼是堆?

堆(heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可被看做一棵樹的數組對象。這裏介紹的是最爲常見的二叉堆。

二叉堆(Binary Heap)是一種特殊的堆,二叉堆是完全二元樹並且總是滿足下列性質:

  • 堆中某個節點的值總是不大於或不小於其父節點的值;
  • 堆總是一棵完全二叉樹。

二叉堆有兩種:最大堆和最小堆。

  • 最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;
  • 最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。

最大堆

二叉堆的數組表示法

如果對於一個二叉堆從上到下、從左至右依次從1開始編號,並將每個節點的編號作爲數組的下標,那麼就能得到一個用數組表示的二叉堆。採用數組表示法有以下優點:

  • 存儲密度高
  • 能夠輕易獲得某個節點的雙親和左右孩子結點

根據二叉樹的性質:下標爲 i(i>1)處節點的雙親節點下標爲 i/2(整除),下標爲 i 處節點的做孩子節點下標爲 2 * i ;右孩子節點下標爲 2 * i + 1。
在這裏插入圖片描述
爲了充分利用數組空間,我們可以從 0 開始編號,那麼下標爲 i(i>0)處節點的雙親節點下標爲 ( i - 1 ) / 2(整除),下標爲 i 處節點的做孩子節點下標爲 2 * i + 1 ;右孩子節點下標爲 2 * i + 2。
在這裏插入圖片描述

大頂堆實現

基於以前實現的動態數組,這裏直接複用SeqList實現大頂堆
數據結構:線性數據結構

大頂堆的向上調整siftUp和向下調整siftDown

siftUp

向大頂堆中添加元素時,由於我們是基於數組實現的,因此可直接在數組末尾添加該元素。但是添加完元素後的堆並不一定滿足大頂堆的性質,因此還需要進行大頂堆的調整(siftUp)。
下面展示了添加元素的過程:
在這裏插入圖片描述
在這裏插入圖片描述
在數組尾部添加52
在這裏插入圖片描述
向上調整,52 大於 16
在這裏插入圖片描述
向上調整,52 大於 41
在這裏插入圖片描述
52小於62 因此不需要再調整,此時就滿足大頂堆的性質了。

向大頂堆中添加元素:

 
//從index處向上調整
    private void siftUp(int index) {

        //index > 0 並且 index處的值大於雙親的值 則進行向上調整爲大頂堆
        while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {

            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    //向大頂堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

由於需要交換數組中的值,因此在SeqList中添加swap方法:

//交換下標爲 i 和 j 下標的值
    public void swap(int i, int j) {
    
        if (i < 0 || i >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }
        if (j < 0 || j >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }
        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
siftDown

需要取出大頂堆的最大元素時,我們可以將數組中最後一個元素放到堆頂,然後進行堆的向下調整。下面展示了取出大頂堆的最大元素的過程:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200212230457965.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDgwODM5,size_16,color_FFFFFF,t_70
在這裏插入圖片描述
將數組最後一個元素放到堆頂。
在這裏插入圖片描述
如果待調整的元素小於它的孩子節點中的最大元素,那麼就和最大元素交換。
16 小於 52 ,因此 16 和 52 發生了交換。
在這裏插入圖片描述
16 小於41 所以交換。
在這裏插入圖片描述
16 大於 15 因此不用再向下調整了,此時的堆滿足大頂堆的性質。

取出最大元素

//從index處向下調整大頂堆
    private void siftDown(int index) {

        while (index * 2 + 1 < data.getSize()) {

            int maxIndex = leftChild(index);

            if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
                maxIndex = rightChild(index);
            }//此時maxIndex爲孩子元素中最大的元素

            //若index處的元素小於maIndex的元素則交換 否則結束
            if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
                data.swap(index, maxIndex);
                index = maxIndex;
            }else {
                break;
            }
        }
    }

    //取出最大元素
    public E extractMax() {

        E ret = getMax();
        //將最後一個元素放到堆頂 向下調整大頂堆
        data.set(0, data.removeLast());
        siftDown(0);

        return ret;
    }
    
 //取出最大元素,並用e替換
    public E replace(E e){

        E ret = getMax();
        data.set(0, e);
        siftDown(0);

        return ret;
    }
將數組轉化爲大頂堆Heapify

將數組轉化爲大頂堆可以在遍歷數組的時候調用上面的add方法,但是如果在原來數組上進行調整可以減少近半的次數,因爲我們可以從第一個非葉子結點開始向下調整。

  //構造方法:Heapify 將一個數組轉化爲大頂堆
    public MaxBinaryHeap(E[] arr) {

        data = new SeqList<E>(arr);
        //從第一個非葉子結點開始,依次向下調整大頂堆
        for (int i = parent(arr.length - 1); i >=0 ; i--) {
            siftDown(i);
        }
    }

同時由於我們底層是用的自己實現的SeqList,因此還需在SeqList添加構造方法

//將數組轉化爲動態數組
public SeqList(T[] arr) {

        data = (T[]) new Object[arr.length];
        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        size = arr.length;
    }

完整代碼

到此,二叉大頂堆基本就實現了,需要注意的是,在實現大頂堆的相關操作時,對以前實現的SeqList增添了一個swap方法和一個構造方法。

MaxBinaryHeap

package cn.boom.heap;

public class MaxBinaryHeap<E extends Comparable<E>> {

    private SeqList<E> data;

    public MaxBinaryHeap(int capacity) {
        this.data = new SeqList<E>(capacity);
    }

    public MaxBinaryHeap() {
        this.data = new SeqList<E>();
    }

    //Heapify 將一個數組轉化爲大頂堆
    public MaxBinaryHeap(E[] arr) {

        data = new SeqList<E>(arr);

        //從第一個非葉子結點開始,依次向下調整大頂堆
        for (int i = parent(arr.length - 1); i >=0 ; i--) {
            siftDown(i);
        }
    }

    //獲取元素個數
    public int getSize() {
        return data.getSize();
    }

    //非空判斷
    public boolean isEmpty() {
        return data.isEmpty();
    }

    //獲取參數下標對應的雙親下標
    private int parent(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if (index == 0) {
            throw new IllegalArgumentException(" index = 0 , no parent !");
        }

        return ( index - 1 ) / 2;
    }

    //獲取參數下標對應的左孩子下標
    private int leftChild(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if ( 2 * index + 1 >= data.getSize()  ) {
            throw new IllegalArgumentException(" index = " + index +" , no leftChild !");
        }

        return 2 * index + 1;
    }

    //獲取參數下標對應的右孩子下標
    private int rightChild(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if ( 2 * index + 2 >= data.getSize()  ) {
            throw new IllegalArgumentException(" index = " + index +" , no rightChild !");
        }

        return 2 * index + 2;
    }

    //從index處向上調整
    private void siftUp(int index) {

        //index > 0 並且 index處的值大於雙親的值 則進行向上調整爲大頂堆
        while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {

            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    //向大頂堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    //獲取最大元素
    public E getMax() {

        if (data.getSize() == 0) {
            throw new IllegalArgumentException("No data , No MaxValue ! ");
        }

        return data.getIndexOf(0);
    }

    //從index處向下調整大頂堆
    private void siftDown(int index) {

        while (index * 2 + 1 < data.getSize()) {

            int maxIndex = leftChild(index);

            if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
                maxIndex = rightChild(index);
            }//此時maxIndex爲孩子元素中最大的元素

            //若index處的元素小於maIndex的元素則交換 否則結束
            if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
                data.swap(index, maxIndex);
                index = maxIndex;
            }else {
                break;
            }
        }
    }

    //取出最大元素
    public E extractMax() {

        E ret = getMax();

        //將最後一個元素放到堆頂 向下調整大頂堆
        data.set(0, data.removeLast());
        siftDown(0);

        return ret;
    }

    //取出最大元素,並用e替換
    public E replace(E e){

        E ret = getMax();

        data.set(0, e);
        siftDown(0);

        return ret;
    }

    @Override
    public String toString() {
        return "MaxBinaryHeap{" +
                "data=" + data +
                '}';
    }
}

修改後的SeqList

package cn.boom.heap;


public class SeqList<T> {

    private T[] data;
    private int size;

    //無參構造
    public SeqList(){
        data = (T[]) new Object[10];
        size = 0;
    }

    //帶參構造 :參數 容量
    public SeqList(int capacity) {
        data = (T[]) new Object[capacity];
        size = 0;
    }


    public SeqList(T[] arr) {

        data = (T[]) new Object[arr.length];

        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }

        size = arr.length;
    }

    /**
     * 獲取真實長度(數據個數)
     * @return size
     */
    public int getSize(){
        return this.size;
    }

    /**
     * 獲取index索引位置的元素
     * @param index
     * @return data[index]
     * @throws IllegalArgumentException 參數不合法異常
     */
    public T getIndexOf(int index) throws IllegalArgumentException{
        //參數合法性校驗
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Illegal Index !");
        }
        return this.data[index];
    }

    //修改值
    public void set(int index, T elem) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Illegal Index !");
        }
        data[index] = elem;
    }

    /**
     * 按值查找 返回elem第一次出現的下標 未找到返回-1
     * @param elem
     * @return index
     */
    public int locationElem(T elem){

        for (int i = 0; i < this.size; i++) {

            if (elem.equals(this.data[i])) {
                return i;
            }
        }
        return -1;
    }

    //是否存在元素
    public boolean contains(T elem){
        return locationElem(elem) != -1;
    }

    //表是否爲空
    public boolean isEmpty(){
        return (size == 0);
    }

    //表是否爲空
    public boolean isFull(){
        return (size == data.length);
    }

    //獲取容量
    public int getCapacity(){
        return data.length;
    }

    //在 index處插入一個元素
    public void add(int index, T elem) {

        //參數合法性校驗

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Illegal Index ! index is " + index);
        }

        if (isFull()) {
            updateCapacity(data.length * 2);
        }

        //在 index 後的數據後移
        for (int i = size; i > index; i--) {
            this.data[i] = this.data[i - 1];
        }

        this.data[index] = elem;

        size++;
    }

    //在數組首部添加元素
    public void addFirst(T elem) {
        add(0, elem);
    }

    //在數組尾部添加元素
    public void addLast( T elem){
        add(this.size,elem);
    }


    //更新數組容量
    public void updateCapacity(int newCapacity) {

        T[] newArray = (T[]) new Object[newCapacity];

        for (int i = 0; i < size; i++) { // copy原數組中的數據
            newArray[i] = data[i];
        }

        this.data = newArray;

    }

    //刪除下標爲index的元素並返回
    public T remove(int index){

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Illegal Index ! index is " + index);
        }

        T elem = data[index];

        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }

        size--;

        if (size < data.length / 2) { //縮容
            updateCapacity(data.length / 2);
        }

        return elem;
    }

    //刪除第一個元素
    public T removeFirst() {
        return remove(0);
    }

    //刪除最後一個元素
    public T removeLast() {
        return remove(size - 1);
    }

    //交換下標爲 i 和 j 下標的值
    public void swap(int i, int j) {

        if (i < 0 || i >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }

        if (j < 0 || j >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }

        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();

        res.append("SeqList{");

        res.append(" data=[");

        for (int i = 0; i < this.size; i++) {

            res.append(this.data[i].toString());

            if (i != size - 1) {
                res.append(',');
            }
        }
        res.append("], size=" + size +  ", capacity=" + getCapacity() + " }");

        return res.toString();
    }
}

基於二叉堆實現優先隊列

普通的隊列是一種先進先出的數據結構,元素在隊列尾追加,而從隊列頭刪除。在優先隊列中,元素被賦予優先級。當訪問元素時,具有最高優先級的元素最先刪除。優先隊列具有最高級先出 (first in, largest out)的行爲特徵。

由於二叉大頂堆的堆頂是堆中最大的元素,基於這個特點,我們很方便的就能基於二叉堆來實現優先隊列這種數據結構。

package cn.boom.queue;

/**
 * 優先隊列
 * @param <E>
 */
public class priorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxBinaryHeap<E> maxBinaryHeap;

    public priorityQueue() {
        this.maxBinaryHeap = new MaxBinaryHeap<E>();
    }

    @Override
    public int getSize() {
        return maxBinaryHeap.getSize();
    }

    @Override
    public boolean isEmpty() {
        return maxBinaryHeap.isEmpty();
    }

    @Override
    public void enQueue(E e) {
        maxBinaryHeap.add(e);
    }

    @Override
    public E deQueue() {
        return maxBinaryHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxBinaryHeap.getMax();
    }
}

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