紙上談兵: 左傾堆 (leftist heap)


作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

 

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

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

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

 

左傾堆 (Leftist Heap)

左傾堆基於二叉樹(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: 對於任意節點的左右兩個子節點,右子節點的npl不大於左子節點的npl

 

左傾堆的性質

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

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

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

  • r = 1, 右側路徑上有一個節點,所以至少有21-1個節點
  • 假設任意r, 左傾堆至少有2r-1節點。那么對於一個右側路徑節點數為r+1的左傾堆來說,根節點的右子樹的右側路徑有r個節點。根節點的左子樹的右側路徑至少有r個節點。根據假設,該左傾堆將包括: 
    • 右子樹:至少有2r-1個節點
    • 左子樹: 至少有2r-1個節點
    • 1個根節點
  • 因此,對於r+1,整個左傾堆至少有2r+1-1個節點。證明完成

 

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

我們將沿着右側路徑進行左傾堆的合並操作。合並采用遞歸。合並如下:

  1. (base case) 如果一個空左傾堆與一個非空左傾堆合並,返回非空左傾堆
  2. 如果兩個左傾堆都非空,那么比較兩個根節點。取較小的根節點為新的根節點(滿足要求1),合並較小根節點堆的右子堆與較大根節點堆。
  3. 如果右子堆npl > 左子堆npl,互換右子堆與左子堆。
  4. 更新根節點的npl = 右子堆npl + 1

上面的合並算法調用了合並操作自身,所以是遞歸。由於我們沿着右側路徑遞歸,所以復雜度是log(n)量級。

 

左傾堆的實現

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

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

  • 插入(insert): 將一個單節點左傾堆(新增節點)與一個已有左傾堆合並
  • 刪除(delete_min): 刪除根節點,將剩余的左右子堆合並

 

/* By Vamei */

/* * leftist heap * bassed on binary tree */ #include <stdio.h> #include <stdlib.h> typedef struct node *position; typedef int ElementTP; struct node { ElementTP element; int npl; position lchild; position rchild; }; 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) { LHEAP h1=NULL; LHEAP h2=NULL; h1 = insert(7, h1); h1 = insert(3, h1); h1 = insert(5, h1); h2 = insert(2, h2); h2 = insert(4, h2); h2 = insert(8, h2); h1 = merge(h1, h2); printf("minimum: %d\n", find_min(h1)); return 0; } /* * insert: * merge a single-node leftist heap with a leftist heap * */ LHEAP insert(ElementTP value, LHEAP h) { LHEAP single; single = (position) malloc(sizeof(struct node)); // initialze
    single->element  = value; single->lchild   = NULL; single->rchild   = NULL; return merge(single, h); } /* * find_min: * return root value in the tree * */ ElementTP find_min(LHEAP h) { if(h != NULL) return h->element; else exit(1); } /* * delete_min: * remove root, then merge two subheaps * */ LHEAP delete_min(LHEAP h) { LHEAP l,r; l = h->lchild; r = h->rchild; free(h); return merge(l, r); } /* * merge two leftist heaps * */ LHEAP merge(LHEAP h1, LHEAP h2) { // if one heap is null, return the other
    if(h1 == NULL) return h2; if(h2 == NULL) return h1; // if both are not null
    if (h1->element < h2->element) { return merge1(h1, h2); } else { return merge1(h2, h1); } } // h1->element < h2->element
static LHEAP merge1(LHEAP h1, LHEAP h2) { if (h1->lchild == NULL) { /* h1 is a single node, npl is 0 */ h1->lchild = h2; /* rchild is NULL, npl of h1 is still 0 */ } else { // left is not NULL // merge h2 to right // swap if necessary
        h1->rchild = merge(h1->rchild, h2); if(h1->lchild->npl < h1->rchild->npl) { swap_children(h1); } h1->npl = h1->rchild->npl + 1; // update npl
 } return h1; } // swap: keep leftist property
static LHEAP swap_children(LHEAP h) { LHEAP tmp; tmp = h->lchild; h->lchild = h->rchild; h->rchild = tmp; }

 

總結

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

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

 

歡迎繼續閱讀“紙上談兵: 算法與數據結構”系列。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM