堆(Heap)的原理與C++實現

1. 什么是堆?

堆(Heap)是一種特殊的樹形數據結構,通常用于實現優先隊列。堆可以分為兩種類型:

  • 最大堆(Max Heap):每個節點的值都大于或等于其子節點的值。
  • 最小堆(Min Heap):每個節點的值都小于或等于其子節點的值。

堆通常是一個完全二叉樹,這意味著除了最后一層,其他層都是完全填滿的,并且最后一層的節點都盡可能地靠左排列。

2. 堆的性質

  • 完全二叉樹:堆是一個完全二叉樹,這意味著它可以用數組來高效地表示。
  • 堆序性質:在最大堆中,父節點的值總是大于或等于其子節點的值;在最小堆中,父節點的值總是小于或等于其子節點的值。

Tips: 堆是完全二叉樹,并非二叉搜索樹

在數據結構中,完全二叉樹二叉搜索樹是兩種常見的樹形結構,它們雖然都屬于二叉樹的范疇,但在定義、性質和應用場景上有顯著的區別。下面我們將詳細分析它們的區別。

特性完全二叉樹二叉搜索樹
定義節點從上到下、從左到右依次填充左子樹 < 根節點 < 右子樹
有序性不一定有序中序遍歷結果有序
結構要求必須是完全填充的(最后一層靠左)無特殊結構要求,只需滿足有序性
查找效率不支持高效查找支持高效查找(平衡時 O(log n))
插入和刪除通常用于堆操作,不支持動態插入刪除支持動態插入和刪除
應用場景堆、優先隊列查找、排序、數據庫索引
數組表示可以用數組高效表示通常用指針或引用表示

完全二叉樹示例

        10/  \5    15/ \   /2   7 12
  • 節點從上到下、從左到右依次填充。
  • 最后一層的節點靠左排列。

二叉搜索樹示例

        10/  \5    15/ \   / \2   7 12 20
  • 左子樹的所有節點值小于根節點,右子樹的所有節點值大于根節點。
  • 中序遍歷結果為 [2, 5, 7, 10, 12, 15, 20],是一個有序序列。

3. 堆的操作

堆的主要操作包括:

  • 插入(Insert):將一個新元素插入堆中,并保持堆的性質。
  • 刪除(Delete):刪除元素,并保持堆的性質。
  • 查詢(Query):查詢堆頂元素
  • 構建堆(Build Heap):將一個無序數組構建成一個堆。

4. 堆的實現

堆通常使用數組來實現。在從數組下標0開始存儲的堆,對于一個索引為 i 的節點:

  • 其父節點的索引為 (i - 1) / 2
  • 其左子節點的索引為 2 * i + 1
  • 其右子節點的索引為 2 * i + 2

4.1 堆的性質維護

堆的插入過程

假設我們有一個最大堆,初始堆為:[100, 19, 36, 17, 3, 25, 1, 2, 7],其對應的完全二叉樹結構如下(用數組表示):

        100/     \19       36/  \     /  \17   3   25   1/ \
2  7

插入一個新元素40
將新元素添加到堆的末尾:堆的數組表示為 [100, 19, 36, 17, 3, 25, 1, 2, 7, 40],對應的完全二叉樹結構如下:

     100/      \19      36/   \    / \17    3  25  1
/ \    /
2  7  40
  1. 向上調整(上浮):從新插入的節點開始,與其父節點比較。如果當前節點的值大于父節點的值,則交換它們的位置。
  • 40 的父節點是 3,40 > 3,交換它們的位置:
        100/      \19       36/  \     /  \17   40  25   1/ \   /2  7  3
  • 40 的新父節點是 19,40 > 19,交換它們的位置:
      100/      \40       36/  \     /  \
17   19  25   1
/ \   /
2  7 3
  • 40 的新父節點是 100,40 < 100,停止調整。
  • 最終,插入 40 后的堆為:[100, 40, 36, 17, 19, 25, 1, 2, 7, 3]

總結:堆在插入元素后,需要進行上浮(up)操作,是不斷與父節點比較,若父節點小于當前節點,則交換位置。具體代碼實現示例如下:

//存儲下標從1開始,以大根堆為例
void heap_up(int idx){while(idx != 1){int parent = idx >> 1;if(heap[parent] < heap[idx]){swap(heap[parent], heap[idx]);idx = parent;}else{break;}}
}
堆的刪除過程

假設我們從上述堆中刪除最大值(堆頂元素 100)。

  1. 將堆頂元素與最后一個元素交換:交換 100 和 3 的位置,得到 [3, 40, 36, 17, 19, 25, 1, 2, 7, 100],然后刪除最后一個元素(100),得到 [3, 40, 36, 17, 19, 25, 1, 2, 7]。這是因為我們在用數組存儲堆的時候,頭部元素的刪除面臨整個數組的移動,相當消耗計算資源,于是我們選擇將頭部元素和尾部元素進行交換,進行刪除尾部,再調整堆
  2. 向下調整(下沉):從堆頂開始,比較當前節點與其子節點的值,將當前節點與較大的子節點交換,直到滿足堆的性質。
  • 當前堆頂是 3,其子節點是 40 和 36,40 > 36,選擇 40 與 3 交換得到:
      40/      \3       36/  \     /  \
17   19  25   1
/ \  
2  7 
  • 3 的新位置是左子樹的根,其子節點是 17 和 19,19 > 17,選擇 19 與 3 交換:
      40/      \19       36/  \     /  \
17   3  25   1
/ \  
2  7 
  • 最終,刪除 100 后的堆為:[40, 19, 36, 17, 3, 25, 1, 2, 7]
void heap_down(int idx){int size = top;while(1){int leftChild = idx * 2;int rightChild = idx * 2 + 1;int largest = idx;if(leftChild < size && heap[largest] < heap[leftChild]){largest = leftChild;}if(rightChild < size && heap[largest] < heap[rightChild]){largest = rightChild;}if (largest != idx) {swap(heap[idx], heap[largest]);idx = largest;} else {break;}}
}

4.2 C++ 實現最大堆

這里展示一個封裝成對象的大根堆

#include <iostream>
#include <vector>
#include <algorithm>class MaxHeap {
private:std::vector<int> heap;void heapifyUp(int index) {while (index > 0) {int parentIndex = (index - 1) / 2;if (heap[index] > heap[parentIndex]) {std::swap(heap[index], heap[parentIndex]);index = parentIndex;} else {break;}}}void heapifyDown(int index) {int size = heap.size();while (true) {int leftChild = 2 * index + 1;int rightChild = 2 * index + 2;int largest = index;if (leftChild < size && heap[leftChild] > heap[largest]) {largest = leftChild;}if (rightChild < size && heap[rightChild] > heap[largest]) {largest = rightChild;}if (largest != index) {std::swap(heap[index], heap[largest]);index = largest;} else {break;}}}public:void insert(int value) {heap.push_back(value);heapifyUp(heap.size() - 1);}int extractMax() {if (heap.empty()) {throw std::out_of_range("Heap is empty");}int maxValue = heap[0];heap[0] = heap.back();heap.pop_back();heapifyDown(0);return maxValue;}void buildHeap(const std::vector<int>& array) {heap = array;for (int i = (heap.size() / 2) - 1; i >= 0; --i) {heapifyDown(i);}}void printHeap() const {for (int value : heap) {std::cout << value << " ";}std::cout << std::endl;}
};int main() {MaxHeap maxHeap;maxHeap.insert(10);maxHeap.insert(20);maxHeap.insert(15);maxHeap.insert(30);maxHeap.insert(40);std::cout << "Max Heap: ";maxHeap.printHeap();std::cout << "Extracted Max: " << maxHeap.extractMax() << std::endl;std::cout << "Max Heap after extraction: ";maxHeap.printHeap();std::vector<int> array = {5, 3, 8, 1, 9};maxHeap.buildHeap(array);std::cout << "Max Heap built from array: ";maxHeap.printHeap();return 0;
}

4.2 代碼解析

  • heapifyUp:用于在插入新元素后,從下往上調整堆,確保堆的性質。
  • heapifyDown:用于在刪除堆頂元素后,從上往下調整堆,確保堆的性質。
  • insert:將新元素插入堆中,并調用 heapifyUp 進行調整。
  • extractMax:刪除并返回堆頂元素,然后調用 heapifyDown 進行調整。
  • buildHeap:將一個無序數組構建成一個堆。

5. 堆的應用

  • 優先隊列:堆是實現優先隊列的理想數據結構,因為可以快速獲取和刪除最大或最小元素。
  • 堆排序:堆排序是一種基于堆的比較排序算法,時間復雜度為 O(n log n)。
  • Dijkstra算法:在圖的單源最短路徑算法中,堆用于高效地選擇下一個要處理的節點。

6. 總結

堆是一種非常高效的數據結構,特別適用于需要頻繁獲取最大或最小元素的場景。通過數組實現堆,可以充分利用其完全二叉樹的性質,使得插入、刪除和構建堆的操作都能在 O(log n) 的時間內完成。

7.練習

ACWing模擬堆
這個題相較普通的堆,增添了一個需要維護的對象,就是這個數字是第幾次插入的。所以我們需要額外維護兩個數組posinv_pos,分別表示第k個插入的數在堆數組中的索引,和堆數組中第i個數是第幾個插入的,完整代碼如下:

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
const int maxn = 1e5 + 7;
int heap[maxn], top;
int pos[maxn], inv_pos[maxn];
void heap_swap(int a, int b){swap(heap[a], heap[b]);swap(pos[inv_pos[a]], pos[inv_pos[b]]);swap(inv_pos[a], inv_pos[b]);
}
void down(int idx){while(1){int leftChild = idx * 2;int rightChild = idx * 2 + 1;int smallest = idx;if(leftChild <= top && heap[leftChild] < heap[smallest])smallest = leftChild;if(rightChild <= top && heap[rightChild] < heap[smallest])smallest = rightChild;if(idx != smallest) {heap_swap(idx, smallest);idx = smallest;}elsebreak;}
}
void up(int idx){while(idx != 1){int parent = idx >> 1;if(heap[parent] > heap[idx]){heap_swap(idx, parent);idx = parent;}else{break;}}
}
int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int n;cin>>n;string op;int x, k, cnt = 0;while(n--){cin>>op;if(op == "I"){cin>>x;heap[++top] = x;pos[++cnt] = top;inv_pos[top] = cnt;up(top);}else if(op == "PM"){cout<<heap[1]<<endl;}else if(op == "DM"){heap_swap(1, top);top--;down(1);}else if(op == "D"){cin>>k;int now = pos[k];heap_swap(now, top);top --;down(now);up(now);}else if(op == "C"){cin>>k>>x;heap[pos[k]] = x;up(pos[k]);down(pos[k]);}}return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894741.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894741.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894741.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

移除元素-雙指針(下標)

題目 給你一個數組 nums 和一個值 val&#xff0c;你需要 原地 移除所有數值等于 val 的元素。元素的順序可能發生改變。然后返回 nums 中與 val 不同的元素的數量。 假設 nums 中不等于 val 的元素數量為 k&#xff0c;要通過此題&#xff0c;您需要執行以下操作&#xff1a…

log4j2日志配置文件

log4j2配置文件每個項目都會用到,記錄一個比較好用的配置文件,方便以后使用時調取,日志輸出級別為debug,也可以修改 <?xml version"1.0" encoding"UTF-8"?> <Configuration monitorInterval"180" packages""><prope…

高等代數筆記—映射與線性空間

映射 映射&#xff1a; σ : M → M ′ \sigma: M \to M σ:M→M′ σ ( a ) a ′ , a ∈ M , a ′ ∈ M ′ \sigma(a)a, a\in M, a \in M σ(a)a′,a∈M,a′∈M′ a ′ a a′是 a a a在 σ \sigma σ下的像&#xff0c; a a a是 a ′ a a′在 σ \sigma σ下的原像 σ : …

提示詞實踐總結

目錄 一、要求創建SqlServer表&#xff08;ChatGpt&#xff09; 二、要求生成多層架構代碼&#xff08;Cursor&#xff09; 三、要求修改方法返回值類型&#xff08;Cursor&#xff09; 四、要求修改方法入參&#xff08;Cursor&#xff09; 五、復雜的多表關聯生成&#…

java進階文章鏈接

java 泛型&#xff1a;java 泛型詳解-絕對是對泛型方法講解最詳細的&#xff0c;沒有之一 Java 泛型&#xff0c;你了解類型擦除嗎&#xff1f; java 注解&#xff1a;深入理解Java注解類型 秒懂&#xff0c;Java 注解 &#xff08;Annotation&#xff09;你可以這樣學 jav…

MyBatis-Plus筆記-快速入門

大家在日常開發中應該能發現&#xff0c;單表的CRUD功能代碼重復度很高&#xff0c;也沒有什么難度。而這部分代碼量往往比較大&#xff0c;開發起來比較費時。 因此&#xff0c;目前企業中都會使用一些組件來簡化或省略單表的CRUD開發工作。目前在國內使用較多的一個組件就是…

Maven jar 包下載失敗問題處理

Maven jar 包下載失敗問題處理 1.配置好國內的Maven源2.重新下載3. 其他問題 1.配置好國內的Maven源 打開??的 Idea 檢測 Maven 的配置是否正確&#xff0c;正確的配置如下圖所示&#xff1a; 檢查項?共有兩個&#xff1a; 確認右邊的兩個勾已經選中&#xff0c;如果沒有請…

Spring 核心技術解析【純干貨版】- IX:Spring 數據訪問模塊 Spring-Jdbc 模塊精講

在現代企業級應用中&#xff0c;數據訪問層的穩定性和高效性至關重要。為了簡化和優化數據庫操作&#xff0c;Spring Framework 提供了 Spring-JDBC 模塊&#xff0c;旨在通過高度封裝的 JDBC 操作&#xff0c;簡化開發者的編碼負擔&#xff0c;減少冗余代碼&#xff0c;同時提…

探秘AI的兩大核心:決策式AI與生成式AI?

目錄 一、引言 二、從定義上來看 1. 決策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#xff09; 三、從技術原理上來看 1. 決策式AI&#xff08;Discriminative AI&#xff09; 2. 生成式AI&#xff08;Generative AI&#…

2.5學習

misc buuctf-假如給我三天光明 下載附件后得到了一個壓縮包和一個圖片&#xff0c;壓縮包為加密壓縮包&#xff0c;需要解出密碼&#xff0c;然后注意到這個圖片并非簡單的一個封面&#xff0c;在下方還有諸多點&#xff0c;有黑有灰。經過搜索&#xff0c;發現這是盲文通過與…

sed變量中特殊字符/處理方式

個人博客地址&#xff1a;sed變量中特殊字符/處理方式 | 一張假鈔的真實世界 如果變量值中包含斜杠&#xff08;/&#xff09;特殊字符&#xff0c;在使用sed命令的做行內字符串替換時可以使用井號&#xff08;#&#xff09;做為sed語法分隔符&#xff0c;如下&#xff1a; G…

java進階1——JVM

java進階——JVM 1、JVM概述 作用 Java 虛擬機就是二進制字節碼的運行環境&#xff0c;負責裝載字節碼到其內部&#xff0c;解釋/編譯為對 應平臺上的機器碼指令行&#xff0c;每一條 java 指令&#xff0c;java 虛擬機中都有詳細定義&#xff0c;如怎么取操 作數&#xff0c…

搭建集成開發環境PyCharm

1.下載安裝Python&#xff08;建議下載并安裝3.9.x&#xff09; https://www.python.org/downloads/windows/ 要注意勾選“Add Python 3.9 to PATH”復選框&#xff0c;表示將Python的路徑增加到環境變量中 2.安裝集成開發環境Pycharm http://www.jetbrains.com/pycharm/…

vue2-v-if和v-for的優先級

vue2-v-if和v-for的優先級 1.v-if和v-for的作用 v-if是條件渲染&#xff0c;只有條件表達式true的情況下&#xff0c;才會渲染v-for是基于一個數組來渲染一個列表&#xff0c;在v-for的時候&#xff0c;保證給每個元素添加獨一無二的key值&#xff0c;便于diff算法進行優化 …

通過C/C++編程語言實現“數據結構”課程中的鏈表

引言 鏈表(Linked List)是數據結構中最基礎且最重要的線性存儲結構之一。與數組的連續內存分配不同,鏈表通過指針將分散的內存塊串聯起來,具有動態擴展和高效插入/刪除的特性。本文將以C/C++語言為例,從底層原理到代碼實現,手把手教你構建完整的鏈表結構,并深入探討其應…

《redis4.0 通信模塊源碼分析(一)》

【redis導讀】redis作為一款高性能的內存數據庫&#xff0c;面試服務端開發&#xff0c;redis是繞不開的話題&#xff0c;如果想提升自己的網絡編程的水平和技巧&#xff0c;redis這款優秀的開源軟件是很值得大家去分析和研究的。 筆者從大學畢業一直有分析redis源碼的想法&…

開源安全一站式構建!開啟企業開源治理新篇章

在如今信息技術日新月異、飛速發展的數字化時代&#xff0c;開源技術如同一股強勁的東風&#xff0c;為企業創新注入了源源不斷的活力&#xff0c;然而&#xff0c;正如一枚硬幣有正反兩面&#xff0c;開源技術的廣泛應用亦伴隨著不容忽視的挑戰。安全風險如影隨形&#xff0c;…

DeePseek結合PS!批量處理圖片的方法教程

? ? 今天我們來聊聊如何利用deepseek和Photoshop&#xff08;PS&#xff09;實現圖片的批量處理。 傳統上&#xff0c;批量修改圖片尺寸、分辨率等任務往往需要編寫腳本或手動處理&#xff0c;而現在有了AI的輔助&#xff0c;我們可以輕松生成PS腳本&#xff0c;實現自動化處…

13.代理模式(Proxy Pattern)

定義 代理模式&#xff08;Proxy Pattern&#xff09; 是一種結構型設計模式&#xff0c;它通過提供一個代理對象來控制對目標對象的訪問。代理對象作為客戶端與目標對象之間的中介&#xff0c;間接地訪問目標對象的功能。代理模式可以在不改變目標對象的情況下增加一些額外的…

DBeaver連接MySQL提示Access denied for user ‘‘@‘ip‘ (using password: YES)的解決方法

在使用DBeaver連接MySQL數據庫時&#xff0c;如果遇到“Access denied for user ip (using password: YES)”的錯誤提示&#xff0c;說明用戶認證失敗。此問題通常與數據庫用戶權限、配置錯誤或網絡設置有關。本文將詳細介紹解決此問題的步驟。 一、檢查用戶名和密碼 首先&am…