資料結構之堆(Heap)

ice_moss發表於2021-03-27

一.堆的定義:

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

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

將根結點最大的堆叫做最大堆或大根堆,根結點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、[斐波那契堆]等。

堆是非線性資料結構,相當於一維陣列,有兩個直接後繼。

二. 邏輯:

堆的定義如下:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關係時,稱之為堆。有如下事實:
Ki >= K(2i),Ki >= K(i+1)或者Ki <= K(2i),Ki <= K(i+1)

若將和此次序列對應的一維陣列(即以一維陣列作此序列的儲存結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必為序列中n個元素的最小值(或最大值)。
這裡我們以最大堆為例進行講解,如下圖:

下圖用將堆的索引標記出:

三.程式碼實現(c++)

如何實現堆呢?

第一步:堆的構建

#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>

using namespace std;
//使用模板函式
template<typename Item>
class MaxHeap{

private:
    Item *data; //作為陣列的指標
    int count; //表示為陣列的索引
    int capacity; //表示堆的容量

public:
    //建構函式
    //構造一個空堆
    MaxHeap(int capacity) {
        //動態的開闢一片空間,並將data指向該空間
        data = new Item[capacity + 1];
        count = 0;
        this->capacity = capacity;
    }
   //解構函式,將new的空間釋放掉
    ~MaxHeap(){
        delete []data;
    }

    //返回堆的大小
    int size(){
        return count ;
    }
    //判斷是否為空堆
    bool is_empty(){
        return count == 0;
    }

};

現在我們就將堆構造完成了,現在我們構建的堆好像沒有什麼用,我們還需要對進行資料的插入(Shift Up)和取出(Shift Down)操作,下面分別來實現。

第二步:Shift Up和Shift Down

1. Shift Up

首先我們需要了解什麼是Shift Up:

​ 就是將堆中插入元素的操作,其邏輯為:從堆後一個節點插入元素,如下圖:

插入元素後必須維護堆的定義,所以需要將新插入的元素做比較,其方法是:

將新元素跟它所在節點的上一個節點 (新元素的父節點) 的數比較,如下圖中:

53 > 16 需要將兩元素位置互換


互換位置後就變成了下圖:

然後仍然需要維護堆的定義所以需要將52和它的父節點的元素做比較:

52 > 41,所以將兩元素位置互換

就得到下圖,但仍然需要維護堆的定義,所以繼續比較

最後就變成了下圖

這就是整個ShiftUp操作

下面上程式碼

//向堆中插入元素
int insert(Item item){
    //我們這裡的對的根節點從索引為1開始,所以需要capacity+1的空間
    //assert用於判斷是否滿足capacity+1 > capacity
    assert(capacity+1 > capacity);
    data[count+1] = item; 
    count++;
    ShiftUp(count);
}

這裡我將建構函式ShiftUp()寫在private中,使用者不需要看到我們背後的邏輯

private:
    Item *data; //作為陣列的指標
    int count; //表示為陣列的索引
    int capacity; //表示堆的容量

    //構造shiftUp
    void ShiftUp(int k){
        while(k>1 && data[k/2] > data[k]){
            swap(data[k/2], data[k]);
            k/=2;
        }
    }

完成了Shift Up,下面我們來完成Shift Down

2.Shift Down

Shift Down是指從堆中將元素取出,其取出操作是:

將堆中的第一個元素取出

然後將堆最後一個元素放到原來取出元素的位置

然後將該元素的左右孩子節點的元素進行比較 (如下圖將52和30比較),然後將該元素和左右孩子節點中值大的元素進行位置互換

如下圖:

然後在將該元素現在所在的節點的左右孩子節點的元素進行比較

得到如下圖:

此時還會判斷16 和15兩元素的大小

此時ShiftDown就完成了

下面上程式碼

Item extractMax(){
    assert(count>0);
    //將第一個元素取出
    Item ret = data[1];
   //將最後一個元素放置第一個位置
    swap(data[1], data[count]);
    //將多餘的位置消去
    count--;
    ShiftDown(1);

    return ret;
}

仍然將ShiftDown放在private中

private:
    Item *data; //作為陣列的指標
    int count; //表示為陣列的索引
    int capacity; //表示堆的容量

    //構造shiftUp
    void shiftUp(int k){
        while(k>1 && data[k/2] > data[k]){
            swap(data[k/2], data[k]);
            k/=2;
        }
    }

    void ShiftDown(int k){
        //判斷是否存在左孩子
        while(2*k < count){
            int j = 2*k;
            //是否存在右孩子
            if(j+1 < count && data[j+1] > data[j])
                j++;
           //data[j]是data[2*k],data[2*k+1]中的最大值
            if(data[k] > data[j])
                break;
            swap(data[k]), data[j];
        }
    }

這樣堆就完成了,下面我們將整個原始碼給出:


#include <iostream>
#include <algorithm>
#include <string>
#include <ctime>
#include <cmath>
#include <cassert>

using namespace std;
//使用模板函式
template<typename Item>
class MaxHeap{

private:
    Item *data; //作為陣列的指標
    int count; //表示為陣列的索引
    int capacity; //表示堆的容量

    //構造shiftUp
    void shiftUp(int k){
        while(k>1 && data[k/2] > data[k]){
            swap(data[k/2], data[k]);
            k/=2;
        }
    }

    void ShiftDown(int k){
        //判斷是否存在左孩子
        while(2*k < count){
            int j = 2*k;
            //是否存在右孩子
            if(j+1 < count && data[j+1] > data[j])
                j++;
           //data[j]是data[2*k],data[2*k+1]中的最大值
            if(data[k] > data[j])
                break;
            swap(data[k]), data[j];
        }
    }

public:
    //建構函式
    //構造一個空堆
    MaxHeap(int capacity) {
        //動態的開闢一片空間,並將data指向該空間
        data = new Item[capacity + 1];
        count = 0;
        this->capacity = capacity;
    }
   //解構函式,將new的空間釋放掉
    ~MaxHeap(){
        delete []data;
    }

    //返回堆的大小
    int size(){
        return count ;
    }
    //判斷是否為空堆
    bool is_empty(){
        return count == 0;
    }

    int insert(Item item){
        //我們這裡的對的根節點從索引為1開始,所以需要capacity+1的空間
        //assert用於判斷是否滿足capacity+1 > capacity
        assert(capacity+1 > capacity);
        data[count+1] = item; //向對插入元素
        count++;
        shiftUp(count);
    }

    Item extractMax(){
        assert(count>0);
        //將第一個元素取出
        Item ret = data[1];

        swap(data[1], data[count]);
        count--;
        ShiftDown(1);

        return ret;
    }

};

(圖片來源:慕課網bobo老師)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章