數據結構之heap算法

文章目錄

  • 前言
  • 1. heap結構概述
  • 2. push_heap
  • 3. pop_heap
  • 4. sort_heap
  • 5. make_heap

前言

  • heap這種數據結構,允許用戶以任何次序將任何數據放入該結構中,但是最后取出數據的時候一定是權值最高(或者最低)的元素。主要和實現有關,根據比較方法的不同,實現大根堆/小根堆的算法……

    下面小編主要是實現大根堆的算法

  • 并且如果一個類型需要進行使用堆來進行存儲,那么這個類型一定是支持比較方法的

本文章就和大家來探討堆的算法。例如:

  1. push_heap
  2. pop_heap
  3. sort_heap
  4. make_heap

1. heap結構概述

  • heap的底層就是使用的一個完全二叉樹(邏輯上)。我們使用vector/array容器來作為heap的底層結構,再以順序結構構建這顆完全二叉樹(物理存儲上)。

  • 大根堆:任意一個父節點的權值都大于等于孩子節點的權值。

  • 小根堆:任意一個父節點的權值都小于等于孩子節點的權值。

在這里插入圖片描述

  • 構建的方式

    1. 我們將這顆二叉樹的根設置為數組的0下標。(還有其它設置方法)

    2. 根節點找孩子節點

      當前節點的下標為i,左孩子的下標為2 * i + 1;右孩子的下標為:2 * i + 2。合理地找到一個根節點左右孩子節點。

    3. 孩子節點找根節點

    當前節點的下標為i,父親節點的下標為(i - 1)/2

  • 現有如圖的大根堆結構:

    構建的一個大根堆結構

2. push_heap

push_heap操作是在原有的堆基礎之上,再在這個完全二叉樹的結構中添加的新的元素,一般而言會有如下的幾個步驟:

  1. 將新數據新增到物理結構的最后下標位置,但是邏輯結構上仍然是一個完全二叉樹。此時,已經破壞了大根堆/小根堆的性質。

  2. 需要對當前節點的數據進行調整,使得整個完全二叉樹滿足大根堆/小跟堆的性質。

    此時我們用到的調整算法:Percolate Up(上溯)。向上調整。

如下圖:

push_heap操作的實現邏輯

  • 調整的結束當前節點權值小于等于父節點權值或者已經更新到根節點
	void push_heap(int val){_heap.push_back(val); //添加新元素AdjustUp(_heap.size() - 1);}void AdjustUp(size_t child){int index = (int)child; //拿到最后一個位置的下標while (index > 0) //當前調整的節點位置大于0,就繼續調整{int parent = (index - 1) / 2; //找到父節點位置if (_heap[parent] < _heap[index]) //父親節點的key < 當前節點位置的key{//交換兩者的權值std::swap(_heap[parent], _heap[index]);}else // >={break;}//調整當前位置的下標index = parent;}}

3. pop_heap

一般來說,pop的操作是取走整個heap的權值最大/最小的節點。根據前面的經驗,那么就是需要將根節點的數據取走。根節點在vector中的下標為0。我們該如何取取走該根節點呢?

  • 為了滿足堆的完全二叉樹的性質,所以pop節點一定是最后一個位置的。那么我們能想到的方案就是:

    1. 交換根節點權值和最后一個節點的權值,執行pop_back。此時整個heap結構仍然保持完全二叉樹結構。但是不滿足大根堆/小根堆性質。

    2. 我們需要對根節點開始調整,使其滿足堆的性質。

      從根節點開始,執行 Percolate Down(下溯)。向下調整。

如下圖:

pop_heap算法演示

  • 調整的結束當前節點權值小于左右孩子最大節點值或者沒有左右孩子
void pop_heap()
{size_t index = _heap.size() - 1;std::swap(_heap[0], _heap[index]); //交換權值_heap.pop_back(); //刪除最后一個元素AdjustDown(0, _heap.size() - 1); //傳入根節點的下標和當前heap的有效長度
}void AdjustDown(int index, int size)
{//在完全二叉樹中,左孩子存在,但是右孩子不一定存在int child = index * 2 + 1; //假設左孩子大while (child < size) //選取的孩子不能越界;越界了說明孩子不存在{if (child + 1 < size && _heap[child] < _heap[child + 1]){child = child + 1; //更新左右孩子中值較大的}if (_heap[index] < _heap[child]) //父親比孩子小{std::swap(_heap[index], _heap[child]); //交換權值//繼續向下調整}else // >={break;}//更新index和childindex = child;child = 2 * index + 1;}
}

后面的sort_heap會解釋,為什么向下調整還需要一個size的參數

4. sort_heap

  • 堆排序的思想

    利用堆的性質:每次都能獲得當前heap中鍵值最大的元素。結合pop_heap算法我們可以可以持續對堆中的元素做pop_heap操作。(注意:這里的pop_heap不會將原有的數據刪除,而是有效數據的位置減少,在pop_heap中的size參數,就是體現有效數據的地方)這樣我們每次都能將鍵值最大/最小的元素安置在容器的末尾,從而完成升序或者降序……

    注意使用完堆排序之后,這個heap就不再是一個heap了

void sort_heap()
{int size = (int)_heap.size(); //得到當前大小while (size > 1) //剩余元素超過兩個{// 模擬pop_backint index = size - 1;std::swap(_heap[0], _heap[index]);--size; //有效長度-1//向下調整AdjustDown(0, size); //向下調整有效長度}
}

5. make_heap

  • 建堆一般是根據一個已有的容器/迭代器中的數據,將這些數據構建出一個大根堆/小根堆的算法。很容易我們可以想到一種方法:

    方案一依次將迭代器區間中的元素push_heap到一個新的堆中

但是這樣的建堆方式是利用了向上調整的算法。這個算法要求,除了最后一個位置之外的所有位置,都滿足堆結構。后面會進行時間復雜度的分析。

  • 如果你比較敏銳,你會發現我們向下調整的算法,要求:當前節點的左右子樹必須滿足堆結構。

    方案二:我們可以局部看待任何一個二叉樹,分為左子樹 根 右子樹。為了實現向下調整的算法。我們似乎的處理方式是:先將左右子樹建堆,最后根向下調整。以任何一個。這樣的建堆方式如下圖:

在這里插入圖片描述
我們采用迭代的方式進行建堆,即:找到沒有成堆的最小樹開始向下調整

void make_heap()
{int index = (int)_heap.size() - 1, size = (int)_heap.size(); //堆數據的大小int parent = (index - 1) / 2; //找到父親節點while (parent >= 0) //實際上調整的節點是父親節點{AdjustDown(parent, size);--parent; //遍歷下一個父親節點}
}
  • 時間復雜度分析
    分析向上建堆和向下建堆的時間復雜度

完。

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

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

相關文章

MCU 軟件斷點調試注意事項!!!

——為什么調試器會在運行中改我的Flash程序&#xff1f;調試單片機時&#xff0c;很多人都有這樣的疑問&#xff1a;明明我在調試前刷進去的固件是好的&#xff0c;為什么加了一個斷點之后&#xff0c;調試器居然去改了 Flash&#xff1f; 如果我拔掉調試器&#xff0c;這個固…

啟發式合并 + 莫隊 戀戀的心跳大冒險

題目來源&#xff1a;2025 Wuhan University of Technology Programming Contest 比賽鏈接&#xff1a;Dashboard - 2025 Wuhan University of Technology Programming Contest - Codeforces 題目大意&#xff1a; Solution&#xff1a; 首先肯定要預處理出以每個節點為起點…

JCTools 無鎖并發隊列基礎:ConcurrentCircularArrayQueue

ConcurrentCircularArrayQueue ConcurrentCircularArrayQueue 是一個抽象類&#xff0c;它為基于數組的并發循環隊列提供了基礎功能。從其命名可以看出幾個關鍵特性&#xff1a;??Concurrent??&#xff1a;常指無鎖并發。??Circular Array??&#xff1a;內部使用循環數…

力扣(LeetCode) ——622. 設計循環隊列(C語言)

題目&#xff1a;622. 設計循環隊列示例1&#xff1a; MyCircularQueue circularQueue new MyCircularQueue(3); // 設置長度為 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.…

在JVM跑JavaScript腳本 | Oracle GraalJS 簡介與實踐

這是2024年初的 GraalVM 系列博文&#xff0c;當時寫了大綱&#xff0c;知道一年半后的現在才得以完成發布&#x1f604; 1、概述 實話說&#xff0c;標題的場景為小眾需求&#xff0c;日常開發基本用不到&#xff0c;我是最近在做一個低代碼輪子玩具 app-meta 需要實現 FaaS&…

基于 EC 數據與大模型技術實現天氣預報:從數據到上線的全棧方法

1. 先校準“EC 數據”與“AI 預報”的語境 EC 數據家族(常用) IFS/HRES:確定性全球模式,水平分辨率約 9 km,常用預報范圍 10 天; IFS/ENS:51 成員集合預報,提供 15 天概率信息; ERA5:再分析數據,小時級、0.25,可追溯至 1940 年,用作訓練/評測黃金基準。 AI 預報…

迭代器模式及優化

迭代器模式&#xff08;Iterator Pattern&#xff09;是一種行為型設計模式&#xff0c;用于提供一種統一的方式遍歷聚合對象&#xff08;如集合、容器&#xff09;中的元素&#xff0c;而無需暴露對象的內部實現細節。它將遍歷邏輯與聚合對象分離&#xff0c;使得遍歷操作可以…

純Qt手撕gb28181協議/gb28181協議服務端/gb28181協議設備端/gb28181設備模擬器/gb28181虛擬監控設備

一、前言說明 搞完onvif設備模擬器&#xff0c;總想著把28181設備模擬也實現&#xff0c;因為之前已經花了大力氣把28181平臺軟件端實現了&#xff0c;為了實現這個組件&#xff0c;頭發掉了一大把&#xff0c;專門把國標文檔看了好幾遍&#xff0c;逐行閱讀&#xff0c;針對需…

【滲透實戰】無下載器環境(curl/wget)下玩轉 Metasploit 自動利用

1. 背景與問題場景 在滲透測試或漏洞利用中&#xff0c;Metasploit&#xff08;MSF&#xff09;是業界最常用的框架之一。 其許多 RCE&#xff08;遠程代碼執行&#xff09;模塊在落地 payload&#xff08;如 Meterpreter 或反彈 shell&#xff09;時&#xff0c;采用了 CMD St…

jd-hotkey探測熱點key

對任意突發性的無法預先感知的熱點數據&#xff0c;包括并不限于熱點數據&#xff08;如突發大量請求同一個商品&#xff09;、熱用戶&#xff08;如惡意爬蟲刷子&#xff09;、熱接口&#xff08;突發海量請求同一個接口&#xff09;等&#xff0c;進行毫秒級精準探測到。然后…

C#WPF實戰出真汁07--【系統設置】--菜品類型設置

1、菜品設置介紹 菜品設置跟餐桌設置的功能目的是相同的&#xff0c;包括了新增&#xff0c;刪除&#xff0c;編輯&#xff0c;分頁&#xff0c;查詢&#xff0c;重置&#xff0c;全選&#xff0c;全消&#xff0c;列表功能&#xff0c;實現流程也是布局設計&#xff0c;后臺邏…

aave v3 存款與借款利息的計算方式

本文只涉及到利率計算的數學原理&#xff0c;不作源碼解析:存款首先我們假設小明在aave里面存了10000usdt&#xff0c;存的時候年化收益率是5%,那么半年后其存款的利息是多少呢?常規的計算方式如下:利息10000*5%*(存款的時長/一年的時長)這么做有什么問題呢&#xff1f;假設現…

Windows MCP.Net:基于.NET的Windows桌面自動化MCP服務器深度解析

&#x1f4cb; 目錄 項目概述 技術架構深度解析 核心功能模塊詳解 代碼實現分析 使用場景與實戰案例 性能優化與最佳實踐 擴展開發指南 總結與展望 項目概述 什么是Windows-MCP.Net&#xff1f; Windows MCP.Net是一個基于.NET 10.0開發的Windows桌面自動化MCP&…

Boost.Asio學習(7):Boost.Beast實現簡易http服務器

namespace beast boost::beast;beast::flat_buffer是一個用于 Boost.Asio 和 Boost.Beast 網絡讀寫的緩沖區實現。專為 一次性順序讀取 / 消費 場景設計&#xff0c;比 std::string 或 std::vector 高效&#xff0c;因為它是扁平內存結構&#xff08;contiguous memory&#x…

深入解析JVM內存區域劃分:從理論到實踐

Java虛擬機&#xff08;JVM&#xff09;是Java程序運行的核心環境&#xff0c;它負責管理內存分配、垃圾回收、字節碼執行等關鍵任務。理解JVM的內存區域劃分&#xff0c;對于優化Java應用性能、排查內存問題&#xff08;如OutOfMemoryError、StackOverflowError&#xff09;至…

滑窗|貪心|?滾動數組

lc17.08pair按身高升序、相同時體重降序排序結果是找體重序列的最長遞增子序列長度核心&#xff1a;轉化為二維最長遞增子序列問題求解vector<int> dp;for (auto& p : hw) {int w p.second;auto it lower_bound(dp.begin(), dp.end(), w);if (it dp.end()) {dp.pu…

深入理解數據庫架構:從原理到實踐的完整指南

一、數據庫存儲架構的多維度分類體系 1.1 基于數據組織方式的存儲架構分類 數據庫的存儲架構從根本上決定了其性能特征、適用場景和擴展能力。理解不同的數據組織方式是選擇合適數據庫技術的基礎&#xff0c;這種分類不僅反映了技術實現的差異&#xff0c;更體現了對不同業務需…

體彩排列三第2025218期號碼分析

大家好&#xff0c;本人蔡楚門來此平臺分享一下本期得經驗和思路&#xff0c;希望能夠給大家帶來好的運氣和靈感&#xff01;體彩排列三第2025218期號碼分析&#xff0c;大小號碼數字分析&#xff0c;上期開出全小號碼最多&#xff0c;最近兩期的開獎號碼全部都是全小號碼最多&…

java設計模式之迪米特法則介紹與說明

一、核心概念與目標 基本定義 迪米特法則的核心思想是&#xff1a;一個對象應該對其他對象盡可能少地了解&#xff0c;僅與直接關聯的對象&#xff08;即“朋友”&#xff09;通信&#xff0c;避免與“陌生人”產生直接交互。 直接朋友&#xff1a;包括當前對象的成員變量、方法…

2024-2025華為ICT大賽中國區 實踐賽昇騰AI賽道(高職組)全國總決賽 理論部分真題+解析

Part 1 昇騰AI全棧系統模塊(共6題)&#xff1a;1、許多計算芯片可以設計作為人工智能的計算芯片&#xff0c;但不同的芯片計算性能不同&#xff0c;昇騰計算芯片是一種()芯片。(單選題)A.CPU B.GPU C. NPU D.TPU正確答案&#xff1a;C解析&#xff1a;A項CPU中央處理器的架…