資料結構之堆:初學只需一文

穩之楠發表於2021-11-07

目錄:

1、定義

2、特性

3、名詞

4、實操

正文:

一、定義:

堆(heap)是電腦科學中一類特殊的資料結構的統稱。
堆通常是一個可以被看做一棵樹的陣列物件。

n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足以下關係時,稱之為堆。
(ki<=k2i且ki<=k2i+1)或者(ki>=k2i且ki>=k2i+1)

解析:
我在這個文章中用的是陣列來實現堆,我就用陣列舉例:
例如:一個陣列[2,1,3,4,5,6,7,8]
這個可以當成一個類似完全二叉樹的結構,根現在是2,但還不是堆,而堆分成最大堆,和最小堆,
我們以最小堆舉例,我們想讓這個完全二叉樹的陣列,變成堆,就需要調整,
讓它們符合根<=左結點並且根<=右結點,在這裡調整我們也分為兩種,
我們可以用向上調整或者向下調整,現在我們使用向下調整法,從頂開始往下調整,
然後向下調整隻要都需要符合堆得屬性,就可以得到最小堆[1,2,3,4,5,6,7,8];

以上對定義以及大致邏輯解析完成,接下來我們完全根據這個邏輯解析做實操程式碼,請耐心往下看

二、特性:

1. 堆中某個結點的值總是不大於或不小於其父結點的值
2. 堆總是一棵完全二叉樹
3. 堆是非線性資料結構,相當於一維陣列,有兩個直接後繼

三、名詞:

最大堆/大根堆/大頂堆:
根結點最大的堆

最小堆/小根堆/小頂堆:
根結點最小的堆

四、實操:

1、建立堆介面類:

/**
 * 堆介面類
 * 應用場景:
 * 優先佇列
 * 取最大值/最小值
 * @author gxw
 * @date 2021/11/4 晚上21:58
 */
public interface Heap {

    //構建堆方法
    void buildHeap(int[] arr);

    //新增元素
    void add(int g);

    //替換元素,將第i個位置的元素值替換為g
    void replace(int i, int g);

    //刪除頂元素
    boolean pop();

    //向上堆排序
    void shift_up(int now);

    //向下堆排序
    void shit_down(int now);

    //獲取頂值
    int getTopValue();

    //展示堆
    void display();
}

2、建立最大堆實現類:

/**
 * 最大堆,也稱大跟堆,也稱大頂堆
 * @author gxw
 * @date 2021/11/4 晚上22:21
 */
public class MaxHeap implements Heap {
    //堆陣列
    private int[] heapArr;

    //堆總結點
    private int size;

    //堆深
    private int depth;

    //堆最大存放結點
    private int maxSize;

    public MaxHeap(int maxSize){
        this.maxSize = maxSize;
        this.heapArr = new int[maxSize];

    }

    @Override
    public void buildHeap(int[] arr) {
        if(arr == null && arr.length <= 0)
            return;
        //進行構建最大堆
        for (int g : arr) {
            add(g);
        }
    }

    @Override
    public void add(int g) {
        //先判斷堆是否已經滿了
        if(size == maxSize)
            return;
        //新增到堆得最後
        heapArr[size] = g;
        //結點數加1
        size++;
        //樹深判斷是否可以加,如果 2^depth -1 < size
        // (原理:通過堆深計算所有總結點值,如果小於當前的堆有結點值,自然需要加1)
        if(Math.pow(2, depth) - 1 < size)
            depth++;
        //向上調整
        shift_up(size);
    }

    @Override
    public void replace(int i, int g) {//i 代表邏輯位置,不代表陣列角標,還需減一
        //判斷如果角標不在正確範圍內,不可以操作
        if(i < 0 || i > size)
            return;
        //修改值
        heapArr[i - 1] = g;
        //向上調整
        shift_up(i);
    }

    @Override
    public boolean pop() {
        if(size == 0)
            return false;
        //移除最大值(大根值),並將最後一個值賦給大根值
        heapArr[0] = heapArr[size - 1];
        //結點數量減一
        size--;
        //向下調整, 1代表邏輯第一個值,不代表陣列第一個
        shit_down(1);
        return true;
    }

    @Override
    public void shift_up(int now) {//now 邏輯數值,不代表物理陣列角標,還需減一
        for(int i = 0;i < depth - 1;i++){
            //獲取父結點的位置,因為是陣列所以減一
            int parentIndex = (int) Math.floor(now / 2) - 1;
            //replace 方法專有,如果父角標小於0,說明到頂結束
            if(parentIndex < 0)
                break;
            int nowIndex = now - 1;//因為是陣列,所以角標-1
            //判斷當前數是否大於父結點,如果大於就交換
            if(heapArr[nowIndex] > heapArr[parentIndex]){
                //交換
                int temp = heapArr[parentIndex];
                heapArr[parentIndex] = heapArr[nowIndex];
                heapArr[nowIndex] = temp;
                //然後從當前繼續往上判斷
                now = parentIndex + 1;
            }else{//如果不大於,就結束調整
                break;
            }
        }
    }

    @Override
    public void shit_down(int now) {//now 邏輯數值,不代表物理陣列角標,還需減一
        for(int i = 0;i < depth - 1;i++){
            //獲取左右子結點值最大的位置
            int sonIndex = heapArr[2 * now - 1] > heapArr[2 * now ] ? 2 * now  - 1 : 2 * now ;
            int nowIndex = now - 1;//因為是陣列,所以角標-1
            //判斷當前數是否小於子結點,如果小於就交換
            if(heapArr[nowIndex] < heapArr[sonIndex]){
                //交換
                int temp = heapArr[sonIndex];
                heapArr[sonIndex] = heapArr[nowIndex];
                heapArr[nowIndex] = temp;
                //然後從當前繼續往下判斷
                now = sonIndex + 1;
            }else{//如果不小於,就結束調整
                break;
            }
        }
    }

    @Override
    public int getTopValue() {
        return heapArr[0];
    }

    @Override
    public void display() {
        for(int i = 0;i < size;i++){
            System.out.print(heapArr[i] + " ");
        }
        System.out.println("");
    }
}

3、建立最小堆實現類:

/**
 * 最小堆,也稱小跟堆,也稱小頂堆
 * @author gxw
 * @date 2021/11/4 晚上22:22
 */
public class MinHeap implements Heap {
    //堆陣列
    private int[] heapArr;

    //堆總結點
    private int size;

    //堆深
    private int depth;

    //堆最小存放結點
    private int maxSize;

    public MinHeap(int maxSize){
        this.maxSize = maxSize;
        this.heapArr = new int[maxSize];

    }

    @Override
    public void buildHeap(int[] arr) {
        if(arr == null && arr.length <= 0)
            return;
        //進行構建最小堆
        for (int g : arr) {
            add(g);
        }
    }

    @Override
    public void add(int g) {
        //先判斷堆是否已經滿了
        if(size == maxSize)
            return;
        //新增到堆得最後
        heapArr[size] = g;
        //結點數加1
        size++;
        //樹深判斷是否可以加,如果 2^depth -1 < size
        // (原理:通過堆深計算所有總結點值,如果小於當前的堆有結點值,自然需要加1)
        if(Math.pow(2, depth) - 1 < size)
            depth++;
        //向上調整
        shift_up(size);
    }

    @Override
    public void replace(int i, int g) {//i 代表邏輯位置,不代表陣列角標,還需減一
        //判斷如果角標不在正確範圍內,不可以操作
        if(i < 0 || i > size)
            return;
        //修改值
        heapArr[i - 1] = g;
        //向上調整
        shift_up(i);
    }

    @Override
    public boolean pop() {
        if(size == 0)
            return false;
        //移除最小值(大根值),並將最後一個值賦給大根值
        heapArr[0] = heapArr[size - 1];
        //結點數量減一
        size--;
        //向下調整, 1代表邏輯第一個值,不代表陣列第一個
        shit_down(1);
        return true;
    }

    @Override
    public void shift_up(int now) {//now 邏輯數值,不代表物理陣列角標,還需減一
        for(int i = 0;i < depth - 1;i++){
            //獲取父結點的位置,因為是陣列所以減一
            int parentIndex = (int) Math.floor(now / 2) - 1;
            //replace 方法專有,如果父角標小於0,說明到頂結束
            if(parentIndex < 0)
                break;
            int nowIndex = now - 1;//因為是陣列,所以角標-1
            //判斷當前數是否小於父結點,如果小於就交換
            if(heapArr[nowIndex] < heapArr[parentIndex]){
                //交換
                int temp = heapArr[parentIndex];
                heapArr[parentIndex] = heapArr[nowIndex];
                heapArr[nowIndex] = temp;
                //然後從當前繼續往上判斷
                now = parentIndex + 1;
            }else{//如果不大於,就結束調整
                break;
            }
        }
    }

    @Override
    public void shit_down(int now) {//now 邏輯數值,不代表物理陣列角標,還需減一
        for(int i = 0;i < depth - 1;i++){
            //獲取左右子結點值最小的位置
            int sonIndex = heapArr[2 * now - 1] < heapArr[2 * now ] ? 2 * now  - 1 : 2 * now ;
            int nowIndex = now - 1;//因為是陣列,所以角標-1
            //判斷當前數是否大於子結點,如果大於就交換
            if(heapArr[nowIndex] > heapArr[sonIndex]){
                //交換
                int temp = heapArr[sonIndex];
                heapArr[sonIndex] = heapArr[nowIndex];
                heapArr[nowIndex] = temp;
                //然後從當前繼續往下判斷
                now = sonIndex + 1;
            }else{//如果不小於,就結束調整
                break;
            }
        }
    }

    @Override
    public int getTopValue() {
        return heapArr[0];
    }

    @Override
    public void display() {
        for(int i = 0;i < size;i++){
            System.out.print(heapArr[i] + " ");
        }
        System.out.println("");
    }
}

4、測試:

public static void main(String[] args) {
    int[] arr = {2,3,3,2,6,7,11,11};

    /**
     * 最大堆,大根堆,大頂堆
     */
    MaxHeap maxHeap = new MaxHeap(20);
    //構建最大堆
    System.out.println("初始構建最大堆");
    maxHeap.buildHeap(arr);
    maxHeap.display();

    //新增數
    System.out.println("新增9");
    maxHeap.add(9);
    maxHeap.display();

    //替換值
    System.out.println("將第6個數值替換成10");
    maxHeap.replace(6, 10);
    maxHeap.display();

    //移除最大值
    System.out.println("移除最大值");
    maxHeap.pop();
    maxHeap.display();

    //獲取最大值
    System.out.println("獲取最大值");
    System.out.println(maxHeap.getTopValue());

    /**
     * 最小堆,小根堆,小頂堆
     */
    MinHeap minHeap = new MinHeap(20);
    //構建最小堆
    System.out.println("初始構建最小堆");
    minHeap.buildHeap(arr);
    minHeap.display();

    //新增數
    System.out.println("新增1");
    minHeap.add(1);
    minHeap.display();

    //替換值
    System.out.println("將第6個數值替換成10");
    minHeap.replace(6, 10);
    minHeap.display();

    //移除最小值
    System.out.println("移除最小值");
    minHeap.pop();
    minHeap.display();

    //獲取最小值
    System.out.println("獲取最小值");
    System.out.println(minHeap.getTopValue());

}

5、結果:

image.png

—————————————END———————————
本文為資料結構堆得學習總結,希望可以幫助到諸位,如果內容有問題,還請貴人指出,謝謝!

相關文章