[數據結構]堆原理及其C++實現

簡介

堆是一種基于完全二叉樹的數據結構.
完全二叉樹:

  1. 每個節點最多有兩個子節點(二叉)
  2. 除了最底層, 其他每一層都必須填滿, 最底層也需要從左到右依次填入數據.

當一棵完全二叉樹滿足下列條件時即稱為堆:

每個父節點都大于等于(或者小于等于)它的兩個子節點.

大于等于的情況稱為大根堆, 小于等于的情況稱為小根堆.
本文以小根堆為例, 大根堆可以很容易類比.
如下圖所示即為一個小根堆:

小根堆

堆的存儲

堆的存儲通過數組來實現, 由于其滿足完全二叉樹的性質.
則有第i個節點(i從0開始算)
父節點: (i-1)/2 // 為負數時則說明父節點不存在
左右子節點: (i*2+1) 和 (i*2+2)

數組存儲

插入堆

給出一個數組存儲的堆, 如果加入了新元素, 必須想辦法保持堆的特性:
完全二叉父節點小于等于其子節點

加入新元素后, 只需要不斷與其父節點進行比較, 根據大小關系進行調整.
即分為兩步:

1.將新的數放入數組尾部.
2.將最后一個數向上調整.

調整
調整后

C++實現(代碼可直接運行查看結果)

#include<iostream>
#include<vector>

using namespace std;

void fix_up(vector <int>& vec)
{
    int pos = vec.size() - 1;
    int n_val = vec[pos];
    int parent = (pos - 1) / 2;

    while (parent >= 0 && n_val < vec[parent]){  // 有父母 且 值小于父母
        swap(vec[parent], vec[pos]);
        pos = parent;
        parent = (pos - 1) / 2;
        n_val = vec[pos];
    }
}

void heap_insert(vector <int>& vec, int val)
{
    vec.push_back(val); // 1.放到尾部
    fix_up(vec); // 2.向上調整
}

int main()
{
    vector <int> vec = { 1,4,8,7,30,10,15 };

    heap_insert(vec, 3);

    for (auto it : vec) {
        cout << it << " ";
    }

    return 0;
}

從堆中刪除

堆結構僅支持從堆頂進行POP操作, 每次都能夠POP出最小的元素.
POP以后堆結構即遭到破壞(缺失了首元素), 此時可以通過下列步驟恢復:

  1. 將最后一個元素放到堆頂.
  2. 將堆頂元素向下調整.

C++實現(代碼可直接運行查看結果)

#include<iostream>
#include<vector>

using namespace std;

void fix_down(vector <int>& vec){
    if (vec.empty())
        return;
    
    int pos = 0;
    int n_val = vec[pos];
    int left = pos * 2 + 1;
    int right = left + 1;

    while (left < vec.size()){
        int *ref;
        int npos;
        if (right < vec.size()) {
            ref = &(vec[left] < vec[right] ? vec[left] :  vec[right]) ; // 跟子節點中較小節點比較.
            npos = vec[left] < vec[right] ? left : right; //下一步的位置
        }
        else {
            ref = &vec[left];
            npos = left;
        }
        if (n_val > *ref) {
            swap(vec[pos], *ref);
        }
        else
            break;
        
        pos = npos;
        left = pos * 2 + 1;
        right = left + 1;
        if(pos < vec.size())
            n_val = vec[pos];
    }
}

void pop(vector <int>& vec)
{
    cout << "pop " << vec[0] << endl;
    vec[0] = vec[vec.size() - 1]; // 1. 把尾部的值放到頭部
    vec.pop_back();
    fix_down(vec); // 2. 向下調整
}

int main()
{
    vector <int> vec = { 1,4,8,7,30,10,15 };

    while (!vec.empty()){
        pop(vec);
    }

    return 0;
}

數組堆化

這一part要解決給出一個數組, 用這個數組構建堆的問題.
解決了堆的插入, 刪除等操作的問題, 再解決堆化的問題就比較容易了.
有以下兩種思路:

  1. 把數組里的數一個一個取出來, 插入堆中.
  2. 對數組里的每一個非葉子節點的數進行向下調整的操作.

以上兩種思路均可以通過上述實現的調整函數進行實現.
注:思路2下, 最后一個非葉子節點的位置為n/2-1, 所以從n/2-1往回遍歷即可.

堆排序

由于堆的頂部總是最小的數, 只需要每次將頂部的數取出, 然后再將堆調整為最小堆即可.
取出一個數, 最多需要調整logN次, 有N個數需要取出
所以堆排序的時間復雜度為NlogN.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容