紙上談兵 左傾堆 leftist heap

2021-09-06 09:06:27 字數 3076 閱讀 3171

我們之前講解了堆(heap)的概念。堆是乙個優先佇列。每次從堆中取出的元素都是堆中優先順序最高的元素。

在之前的文章中,我們基於完全二叉樹(complete binary tree)實現了堆,這樣的堆叫做二叉堆(binary heap)。binary heap有乙個基本要求: 每個節點的優先順序大於兩個子節點的優先順序。在這一要求下,堆的根節點始終是堆的元素中優先順序最高的元素。此外,我們實現了delete_min()操作,從堆中取出元素;insert()操作,向堆中插入元素。

現在,我們考慮下面的問題: 如何合併(merge)兩個堆呢? 乙個方案是從第乙個堆中不斷取出乙個元素,並插入到第二個堆中。這樣,我們需要量級為n的操作。我們下面要實現更有效率的合併。

左傾堆基於二叉樹

(binary tree)。左傾堆的節點滿足堆的基本要求,即(要求1)每個節點的優先順序大於子節點的優先順序。與二叉堆

不同,左傾堆並不是完全二叉樹。二叉堆是非常平衡的樹結構,它的每一層都被填滿(除了最下面一層)。左傾堆則是維持一種不平衡的結構: 它的左子樹節點往往比右子樹有更多的節點。

不平衡

左傾堆的每個節點有乙個附加資訊,即null path length (npl)。npl是從乙個節點到乙個最近的不滿節點的路徑長度(不滿節點:兩個子節點至少有乙個為null)。乙個葉節點的npl為0,乙個null節點的npl為-1。

各個節點的npl (這裡顯示的不是元素值)

根據npl的定義,我們有推論1: 乙個節點的npl等於子節點npl中最小值加1: npl(node) = min(npl(lchild), npl(rchild)) + 1

有了npl的概念,我們可以完整的定義左傾堆。左傾堆是乙個符合下面要求的二叉樹:

從上面的要求1和2可以知道,左傾堆的任意子樹也是乙個左傾堆。

由於左傾堆的特徵,左傾堆的右側路徑(right path)

較短。右側路徑是指我們從根節點開始,不斷前往右子節點所構成的路徑。對於乙個左傾堆來說,右側路徑上節點數不大於任意其他路徑上的節點數,否則,將違反左傾堆的要求2。

我們還可以證明推論2,如果乙個左傾堆的右側路徑上有r個節點,那麼該左傾堆將至少有2r-1個節點。我們採用歸納法證明:

因此,對於r+1,整個左傾堆至少有2r+1-1個節點。證明完成

換句話說,乙個n節點的的左傾堆,它的右側路徑最多有log(n+1)個節點。如果對右側路徑進行操作,其複雜度將是log(n)量級。

我們將沿著右側路徑進行左傾堆的合併操作。合併採用遞迴。合併如下:

(base case) 如果乙個空左傾堆與乙個非空左傾堆合併,返回非空左傾堆

如果兩個左傾堆都非空,那麼比較兩個根節點。取較小的根節點為新的根節點(滿足要求1),合併較小根節點堆的右子堆與較大根節點堆。

如果右子堆npl > 左子堆npl,互換右子堆與左子堆。

更新根節點的npl = 右子堆npl + 1

上面的合併演算法呼叫了合併操作自身,所以是遞迴。由於我們沿著右側路徑遞迴,所以複雜度是log(n)量級。

上面可以看到,左傾堆可以相對高效的實現合併(merge)操作。

其他的堆操作,比如insert, delete_min都可以在merge基礎上實現:

/*

by vamei

*//*

* leftist heap

* bassed on binary tree */

#include

#include

typedef

struct node *position;

typedef

intelementtp;

struct

node ;

typedef

struct node *lheap;

lheap insert(elementtp, lheap);

elementtp find_min(lheap);

lheap delete_min(lheap);

lheap merge(lheap, lheap);

static

lheap merge1(lheap, lheap);

static

lheap swap_children(lheap);

int main(void)/*

* insert:

* merge a single-node leftist heap with a leftist heap

* */

lheap insert(elementtp value, lheap h)

/** find_min:

* return root value in the tree

* */

elementtp find_min(lheap h)

/** delete_min:

* remove root, then merge two subheaps

* */

lheap delete_min(lheap h)

/** merge two leftist heaps

* */

lheap merge(lheap h1, lheap h2)

else}//

h1->element < h2->element

static

lheap merge1(lheap h1, lheap h2)

else

h1->npl = h1->rchild->npl + 1; //

update npl

}

return

h1;}

//swap: keep leftist property

static

lheap swap_children(lheap h)

左傾堆利用不平衡的節點分布,讓右側路徑保持比較短的狀態,從而提高合併的效率。

在合併過程,通過左右互換,來恢復左傾堆的性質。

紙上談兵 佇列 queue

佇列 queue 是乙個簡單而常見的資料結構。佇列也是有序的元素集合。佇列最大的特徵是first in,first out fifo,先進先出 即先進入佇列的元素,先被取出。這一點與棧 stack 形成有趣的對比。佇列在生活中很常見,排隊買票 排隊等車 先到的人先得到服務並離開佇列,後來的人加入到佇...

紙上談兵 佇列 queue

佇列 queue 是乙個簡單而常見的資料結構。佇列也是有序的元素集合。佇列最大的特徵是first in,first out fifo,先進先出 即先進入佇列的元素,先被取出。這一點與棧 stack 形成有趣的對比。佇列在生活中很常見,排隊買票 排隊等車 先到的人先得到服務並離開佇列,後來的人加入到佇...

紙上談兵 棧 stack

棧 stack 是簡單的資料結構,但在計算機中使用廣泛。它是有序的元素集合。棧最顯著的特徵是lifo last in,first out,後進先出 當我們往箱子裡存放一疊書時,先存放的書在箱子下面,我們必須將後存放的書取出來,才能看到和拿出早先存放的書。棧中的每個元素稱為乙個frame。而最上層元素...