數據結構——配對堆

引入

配對堆是一個支持插入查詢/刪除最小值合并修改元素等操作的數據結構,是一種可并堆。有速度快和結構簡單的優勢,但由于其為基于勢能分析的均攤復雜度,無法可持久化。

定義

配對堆是一棵滿足堆性質的帶權多叉樹(如下圖),即每個節點的權值都小于或等于他的所有兒子(以小根堆為例,下同)。
在這里插入圖片描述

通常我們使用兒子 - 兄弟表示法儲存一個配對堆(如下圖),一個節點的所有兒子節點形成一個單向鏈表。每個節點儲存第一個兒子的指針,即鏈表的頭節點;和他的右兄弟的指針。

這種方式便于實現配對堆,也將方便復雜度分析。

在這里插入圖片描述

struct Node {T v;  // T為權值類型Node *child, *sibling;// child 指向該節點第一個兒子,sibling 指向該節點的下一個兄弟。// 若該節點沒有兒子/下個兄弟則指針指向 nullptr。
};

從定義可以發現,和其他常見的堆結構相比,配對堆不維護任何額外的樹大小,深度,排名等信息(二叉堆也不維護額外信息,但它是通過維持一個嚴格的完全二叉樹結構來保證操作的復雜度),且任何一個滿足堆性質的樹都是一個合法的配對堆,這樣簡單又高度靈活的數據結構奠定了配對堆在實踐中優秀效率的基礎;作為對比,斐波那契堆糟糕的常數就是因為它需要維護很多額外的信息。

配對堆通過一套精心設計的操作順序來保證它的總復雜度,原論文1將其稱為「一種自調整的堆(Self Adjusting Heap)」。在這方面和 Splay 樹(在原論文中被稱作「Self Adjusting Binary Tree」)頗有相似之處。

過程

查詢最小值

從配對堆的定義可看出,配對堆的根節點的權值一定最小,直接返回根節點即可。

合并

合并兩個配對堆的操作很簡單,首先令兩個根節點較小的一個為新的根節點,然后將較大的根節點作為它的兒子插入進去。(見下圖)

在這里插入圖片描述
需要注意的是,一個節點的兒子鏈表是按插入時間排序的,即最右邊的節點最早成為父節點的兒子,最左邊的節點最近成為父節點的兒子。

實現

Node* meld(Node* x, Node* y) {// 若有一個為空則直接返回另一個if (x == nullptr) return y;if (y == nullptr) return x;if (x->v > y->v) std::swap(x, y);  // swap后x為權值小的堆,y為權值大的堆// 將y設為x的兒子y->sibling = x->child;x->child = y;return x;  // 新的根節點為 x
}

插入

合并都有了,插入就直接把新元素視為一個新的配對堆和原堆合并就行了。

刪除最小值

首先要提及的一點是,上文的幾個操作都十分偷懶,完全沒有對數據結構進行維護,所以我們需要小心設計刪除最小值的操作,來保證總復雜度不出問題。

根節點即為最小值,所以要刪除的是根節點。考慮拿掉根節點之后會發生什么:根節點原來的所有兒子構成了一片森林;而配對堆應當是一棵樹,所以我們需要通過某種順序把這些兒子全部合并起來。

一個很自然的想法是使用 m e l d meld meld 函數把兒子們從左到右挨個并在一起,這樣做的話正確性是顯然的,但是會導致單次操作復雜度退化到 O ( n ) O(n) O(n)

為了保證總的均攤復雜度,需要使用一個「兩步走」的合并方法:

1.把兒子們兩兩配成一對,用 meld 操作把被配成同一對的兩個兒子合并到一起(見下圖 1),
2.將新產生的堆** 從右往左**(即老的兒子到新的兒子的方向)挨個合并在一起(見下圖 2)。

在這里插入圖片描述
在這里插入圖片描述

先實現一個輔助函數merges,作用是合并一個節點的所有兄弟。

實現

Node* merges(Node* x) {if (x == nullptr || x->sibling == nullptr)return x;  // 如果該樹為空或他沒有下一個兄弟,就不需要合并了,return。Node* y = x->sibling;                // y 為 x 的下一個兄弟Node* c = y->sibling;                // c 是再下一個兄弟x->sibling = y->sibling = nullptr;   // 拆散return meld(merges(c), meld(x, y));  // 核心部分
}

最后一句話是該函數的核心,這句話分三部分:
1.meld(x,y)「配對」了 x 和 y。
2.merges( c ) 遞歸合并 c 和他的兄弟們。
3.將上面 2 個操作產生的 2 個新樹合并。

需要注意到的是,上文提到了第二步時的合并方向是有要求的(從右往左合并),該遞歸函數的實現已保證了這個順序,如果讀者需要自行實現迭代版本的話請務必注意保證該順序,否則復雜度將失去保證。

有了 merges 函數,delete-min 操作就顯然了。

Node* delete_min(Node* x) {Node* t = merges(x->child);delete x;  // 如果需要內存回收return t;
}

減小一個元素的值

要實現這個操作,需要給節點添加一個「父」指針,當節點有左兄弟時,其指向左兄弟而非實際的父節點;否則,指向其父節點。

首先節點的定義修改為:

struct Node {LL v;int id;Node *child, *sibling;Node *father;  // 新增:父指針,若該節點為根節點則指向空節點 nullptr
};

meld 操作修改為:

Node* meld(Node* x, Node* y) {if (x == nullptr) return y;if (y == nullptr) return x;if (x->v > y->v) std::swap(x, y);if (x->child != nullptr) {  // 新增:維護父指針x->child->father = y;}y->sibling = x->child;y->father = x;  // 新增:維護父指針x->child = y;return x;
}

merges操作修改為:

Node *merges(Node *x) {if (x == nullptr) return nullptr;x->father = nullptr;  // 新增:維護父指針if (x->sibling == nullptr) return x;Node *y = x->sibling, *c = y->sibling;y->father = nullptr;  // 新增:維護父指針x->sibling = y->sibling = nullptr;return meld(merges(c), meld(x, y));
}

現在我們來考慮如何實現 decrease-key 操作。
首先我們發現,當我們減少節點 x 的權值之后,以 x 為根的子樹仍然滿足配對堆性質,但 x 的父親和 x 之間可能不再滿足堆性質。
因此我們把整棵以 x 為根的子樹剖出來,現在兩棵樹都符合配對堆性質了,然后把他們合并起來,就完成了全部操作。

// root為堆的根,x為要操作的節點,v為新的權值,調用時需保證 v <= x->v
// 返回值為新的根節點
Node *decrease_key(Node *root, Node *x, LL v) {x->v = v;                 // 更新權值if (x == root) return x;  // 如果 x 為根,則直接返回// 把x從fa的子節點中剖出去,這里要分x的位置討論一下。if (x->father->child == x) {x->father->child = x->sibling;} else {x->father->sibling = x->sibling;}if (x->sibling != nullptr) {x->sibling->father = x->father;}x->sibling = nullptr;x->father = nullptr;return meld(root, x);  // 重新合并 x 和根節點
}

復雜度分析

配對堆結構與實現簡單,但時間復雜度分析并不容易。

原論文1僅將復雜度分析到 melddelete-min 操作均為均攤 O ( log ? n ) O(\log n) O(logn),但提出猜想認為其各操作都有和斐波那契堆相同的復雜度。

遺憾的是,后續發現,不維護額外信息的配對堆,在特定的操作序列下,decrease-key 操作的均攤復雜度下界至少為 Ω ( log ? log ? n ) 2 \Omega (\log \log n)2 Ω(loglogn)2

目前對復雜度上界比較好的估計有,Iacono 的 O ( 1 ) O(1) O(1) meld,$O(\log n) $decrease;Pettie 的 O ( 2 2 log ? log ? n ) O(2^{2 \sqrt{\log \log n}}) O(22loglogn ?) meld 和 decrease。需要注意的是,前述復雜度均為均攤復雜度,因此不能對各結果分別取最小值。

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

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

相關文章

C語言暑假刷題沖刺篇——day1

目錄 一、選擇題 二、編程題 &#x1f388;個人主頁&#xff1a;庫庫的里昂 &#x1f390;CSDN新晉作者 &#x1f389;歡迎 &#x1f44d;點贊?評論?收藏?收錄專欄&#xff1a;C語言每日一練 ?其他專欄&#xff1a;代碼小游戲C語言初階&#x1f91d;希望作者的文章能對你…

問道管理:網上如何打新股?

隨著資本市場的不斷敞開&#xff0c;越來越多的人開始重視股票市場&#xff0c;并想經過網上打新股來取得更大的出資收益。但是&#xff0c;網上打新股的辦法并不簡略&#xff0c;怎樣才能成功地打新股呢&#xff1f;本文將從多個角度剖析&#xff0c;協助廣闊出資者處理這一問…

海信聚好看將攜新品DBdoctor,亮相中國數據庫技術大會(DTCC2023)

海信聚好看將攜新品DBdoctor&#xff0c;亮相中國數據庫技術大會 8月16日—18日&#xff0c;第14屆中國數據庫技術大會&#xff08;DTCC-2023&#xff09;將在北京國際會議中心隆重召開。作為國內數據庫領域規模最大的技術交流盛會&#xff0c;吸引了眾多業內知名企業和數百名…

[謙實思紀 01]整理自2023雷軍年度演講——《成長》(上篇)武大回憶(夢想與成長)

文章目錄 [謙實思紀]整理自2023雷軍年度演講 ——《成長》&#xff08;上篇&#xff09;武大回憶&#xff08;夢想與成長&#xff09;0. 寫在前面1. 夢開始的地方1.1 要有夢想&#xff0c;要用目標量化夢想 2. 在兩年內修完所有的學分。2.1 別老自己琢磨&#xff0c;找個懂的人…

【LeetCode 算法】Matrix Diagonal Sum 矩陣對角線元素的和

文章目錄 Matrix Diagonal Sum 矩陣對角線元素的和問題描述&#xff1a;分析代碼Math Tag Matrix Diagonal Sum 矩陣對角線元素的和 問題描述&#xff1a; 給你一個正方形矩陣 mat&#xff0c;請你返回矩陣對角線元素的和。 請你返回在矩陣主對角線上的元素和副對角線上且不…

Python爬蟲IP代理池的建立和使用

寫在前面 建立Python爬蟲IP代理池可以提高爬蟲的穩定性和效率&#xff0c;可以有效避免IP被封鎖或限制訪問等問題。 下面是建立Python爬蟲IP代理池的詳細步驟和代碼實現&#xff1a; 1. 獲取代理IP 我們可以從一些代理IP網站上獲取免費或付費的代理IP&#xff0c;或者自己租…

【深度學習所有損失函數】在 NumPy、TensorFlow 和 PyTorch 中實現(1/2)

一、說明 在本文中&#xff0c;討論了深度學習中使用的所有常見損失函數&#xff0c;并在NumPy&#xff0c;PyTorch和TensorFlow中實現了它們。 二、內容提要 我們本文所談的代價函數如下所列&#xff1a; 均方誤差 &#xff08;MSE&#xff09; 損失二進制交叉熵損失加權二進…

“深入解析JVM內部機制:探索Java虛擬機的奧秘“

標題&#xff1a;深入解析JVM內部機制&#xff1a;探索Java虛擬機的奧秘 JVM&#xff08;Java虛擬機&#xff09;是Java程序的核心執行環境&#xff0c;它負責將Java字節碼轉換為機器碼并執行。了解JVM的內部機制對于理解Java程序的執行過程和性能優化至關重要。本文將深入解析…

開啟想象翅膀:輕松實現文本生成模型的創作應用,支持LLaMA、ChatGLM、UDA、GPT2、Seq2Seq、BART、T5、SongNet等模型,開箱即用

開啟想象翅膀&#xff1a;輕松實現文本生成模型的創作應用&#xff0c;支持LLaMA、ChatGLM、UDA、GPT2、Seq2Seq、BART、T5、SongNet等模型&#xff0c;開箱即用 TextGen: Implementation of Text Generation models 1.介紹 TextGen實現了多種文本生成模型&#xff0c;包括&a…

c++——::作用域、命名空間、using(聲明和編譯指令)

c 作用域和名字控制 一、::(雙冒號) 作用域 <::>運算符是一個作用域如果<::>前面什么都沒有加 代表是全局作用域 二、命名空間&#xff08;namespace) 1、namespace 本質是作用域,可以更好的控制標識符的作用域命名空間 就可以存放 變量 函數 類 結構體 … 2…

【kubernetes】在k8s集群環境上,部署kubesphere

部署kubesphere 學習于尚硅谷kubesphere課程 前置環境配置-部署默認存儲類型 這里使用nfs #所有節點安裝 yum install -y nfs-utils# 在master節點執行以下命令 echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports # 執行以下命令&#xff…

QML與C++交互

目錄 1 QML獲取C的變量值 2 QML獲取C創建的自定義對象 3 QML發送信號綁定C端的槽 4 C端發送信號綁定qml端槽 5 C調用QML端函數 1 QML獲取C的變量值 QQmlApplicationEngine engine; 全局對象 上下文屬性 QQmlApplicationEngine engine; QQmlContext *context1 engine.…

flowable流程移植新項目前端問題匯總

flowable流程移植到新項目時&#xff0c;出現一些前端問題&#xff0c;匯總如下&#xff1a; PS F:\khxm\NBCIO_VUE> yarn run serve yarn run v1.21.1 $ vue-cli-service serve INFO Starting development server... ERROR Error: Vue packages version mismatch: -…

25 | 葡萄酒質量數據分析

基于kaggle提供的公開數據集,對全球葡萄酒分布情況和質量情況進行數據探索和分析 from kaggle: https://www.kaggle.com/zynicide/wine-reviews 分析思路: 0、數據準備 1、葡萄酒的種類 2、葡萄酒質量 3、葡萄酒價格 4、葡萄酒描述詞庫 5、品鑒師信息 6、總結 0、數據準備 …

學習Vue:組件的概念和優勢

在現代的前端開發中&#xff0c;組件化開發是一種重要的方法&#xff0c;它可以將復雜的應用程序拆分成多個獨立的、可復用的組件。Vue.js 是一個流行的前端框架&#xff0c;它支持組件化開發&#xff0c;讓開發者能夠更輕松地構建和維護復雜的用戶界面。在本文中&#xff0c;我…

計算機組成部分

計算機的五大部件是什么&#xff1f;答案&#xff1a;計算機的五大部件是運算器&#xff0c;控制器&#xff0c;存儲器&#xff0c;輸入設備和輸出設備。 其中運算器和控制器合稱中央處理器&#xff0c;是計算機的核心部件&#xff1b; 存儲器是用來存儲程序指令和數據用的&am…

修改第三方組件默認樣式

深度選擇器 修改el-input的樣式&#xff1a; <el-input class"input-area"></el-input>查看DOM結構&#xff1a; 原本使用 /deep/ 但是可能不兼容 使用 :deep .input-area {:deep(.el-input__inner){background-color: blue;} }將 input 框背景色改為…

【Kubernetes】Kubernetes的Pod進階

Pod進階 一、資源限制和重啟策略1. 資源限制2. 資源單位2.1 CPU 資源單位2.2 內存 資源單位 3. 重啟策略&#xff08;restartPolicy&#xff09; 二、健康檢查的概念1. 健康檢查1.1 探針的三種規則1.2 Probe 支持三種檢查方法 2. 示例2.1 exec 方式2.2 httpGet 方式2.3 tcpSock…

臨床試驗三原則-對照、重復、隨機

臨床試驗必須遵循三個基本原則&#xff1a;對照、重復、隨機。 一、對照原則和對照的設置 核心觀點&#xff1a;有比較才有鑒別。 對照組和試驗組同質可比。 三臂試驗 安慰劑&#xff1a;試驗組&#xff1a;陽性對照組1&#xff1a;n&#xff1a;m&#xff08;n≥m&#xff…

FFmpeg常見命令行(五):FFmpeg濾鏡使用

前言 在Android音視頻開發中&#xff0c;網上知識點過于零碎&#xff0c;自學起來難度非常大&#xff0c;不過音視頻大牛Jhuster提出了《Android 音視頻從入門到提高 - 任務列表》&#xff0c;結合我自己的工作學習經歷&#xff0c;我準備寫一個音視頻系列blog。本文是音視頻系…