左傾堆(一)之 圖文解析 和 C語言的實現

 

概要

本章介紹左傾堆,它和二叉堆同樣,都是堆結構中的一員。和以往同樣,本文會先對左傾堆的理論知識進行簡單介紹,而後給出C語言的實現。後續再分別給出C++和Java版本的實現;實現的語言雖不一樣,可是原理一模一樣,選擇其中之一進行了解便可。若文章有錯誤或不足的地方,請不吝指出! html

目錄
1. 左傾堆的介紹
2. 左傾堆的圖文解析
3. 左傾堆的C實現(完整源碼)
4. 左傾堆的C測試程序node

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3638327.html算法


更多內容:數據結構與算法系列 目錄數據結構

(01) 左傾堆(一)之 圖文解析 和 C語言的實現
(02) 左傾堆(二)之 C++的實現
(03) 左傾堆(三)之 Java的實現
ide

 

左傾堆的介紹

左傾堆(leftist tree 或 leftist heap),又被成爲左偏樹、左偏堆,最左堆等。
它和二叉堆同樣,都是優先隊列實現方式。當優先隊列中涉及到"對兩個優先隊列進行合併"的問題時,二叉堆的效率就沒法使人滿意了,而本文介紹的左傾堆,則能夠很好地解決這類問題。post

 

左傾堆的定義測試

左傾堆是一棵二叉樹,它的節點除了和二叉樹的節點同樣具備左右子樹指針外,還有兩個屬性:鍵值零距離
(01) 鍵值的做用是來比較節點的大小,從而對節點進行排序。
(02) 零距離(英文名NPL,即Null Path Length)則是從一個節點到一個"最近的不滿節點"的路徑長度。不滿節點是指該該節點的左右孩子至少有有一個爲NULL。葉節點的NPL爲0,NULL節點的NPL爲-1。
spa

上圖是一顆左傾堆,它知足左傾堆的基本性質:
[性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。
[性質2] 節點的左孩子的NPL >= 右孩子的NPL。
[性質3] 節點的NPL = 它的右孩子的NPL + 1。3d

左傾堆,顧名思義,是有點向左傾斜的意思了。它在統計問題、最值問題、模擬問題和貪心問題等問題中有着普遍的應用。此外,斜堆是比左傾堆更爲通常的數據結構。固然,今天討論的是左傾堆,關於斜堆,之後再撰文來表。
前面說過,它能和好的解決"兩個優先隊列合併"的問題。實際上,左傾堆的合併操做的平攤時間複雜度爲O(lg n),而徹底二叉堆爲O(n)。合併就是左傾樹的重點,插入和刪除操做都是以合併操做爲基礎的。插入操做,能夠看做兩顆左傾樹合併;刪除操做(移除優先隊列中隊首元素),則是移除根節點以後再合併剩餘的兩個左傾樹。閒話說到這裏,下面開始介紹左傾樹的基本方法。指針

 

左傾堆的圖文解析

合併操做是左傾堆的重點。合併兩個左傾堆的基本思想以下:
(01) 若是一個空左傾堆與一個非空左傾堆合併,返回非空左傾堆。
(02) 若是兩個左傾堆都非空,那麼比較兩個根節點,取較小堆的根節點爲新的根節點。將"較小堆的根節點的右孩子"和"較大堆"進行合併。
(03) 若是新堆的右孩子的NPL > 左孩子的NPL,則交換左右孩子。
(04) 設置新堆的根節點的NPL = 右子堆NPL + 1

 

下面經過圖文演示合併如下兩個堆的過程。


提示:這兩個堆的合併過程和測試程序相對應!

第1步:將"較小堆(根爲10)的右孩子"和"較大堆(根爲11)"進行合併。
合併的結果,至關於將"較大堆"設置"較小堆"的右孩子,以下圖所示:

 

第2步:將上一步獲得的"根11的右子樹"和"根爲12的樹"進行合併,獲得的結果以下:

 

第3步:將上一步獲得的"根12的右子樹"和"根爲13的樹"進行合併,獲得的結果以下:

 

第4步:將上一步獲得的"根13的右子樹"和"根爲16的樹"進行合併,獲得的結果以下:

 

第5步:將上一步獲得的"根16的右子樹"和"根爲23的樹"進行合併,獲得的結果以下:

至此,已經成功的將兩棵樹合併成爲一棵樹了。接下來,對新生成的樹進行調節。

 

第6步:上一步獲得的"樹16的右孩子的NPL > 左孩子的NPL",所以交換左右孩子。獲得的結果以下:

 

第7步:上一步獲得的"樹12的右孩子的NPL > 左孩子的NPL",所以交換左右孩子。獲得的結果以下:

 

第8步:上一步獲得的"樹10的右孩子的NPL > 左孩子的NPL",所以交換左右孩子。獲得的結果以下:

至此,合併完畢。上面就是合併獲得的左傾堆!


下面看看左傾堆的基本操做的代碼

1. 頭文件

#ifndef _LEFTIST_TREE_H_
#define _LEFTIST_TREE_H_

typedef int Type;

typedef struct _LeftistNode{
    Type   key;                    // 關鍵字(鍵值)
    int npl;                    // 零路經長度(Null Path Length)
    struct _LeftistNode *left;    // 左孩子
    struct _LeftistNode *right;    // 右孩子
}LeftistNode, *LeftistHeap;

// 前序遍歷"左傾堆"
void preorder_leftist(LeftistHeap heap);
// 中序遍歷"左傾堆"
void inorder_leftist(LeftistHeap heap);
// 後序遍歷"左傾堆"
void postorder_leftist(LeftistHeap heap);

// 獲取最小值(保存到pval中),成功返回0,失敗返回-1。
int leftist_minimum(LeftistHeap heap, int *pval);
// 合併"左傾堆x"和"左傾堆y",並返回合併後的新樹
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y);
// 將結點插入到左傾堆中,並返回根節點
LeftistNode* insert_leftist(LeftistHeap heap, Type key);
// 刪除結點(key爲節點的值),並返回根節點
LeftistNode* delete_leftist(LeftistHeap heap);

// 銷燬左傾堆
void destroy_leftist(LeftistHeap heap);

// 打印左傾堆
void print_leftist(LeftistHeap heap);

#endif

LeftistNode是左傾堆對應的節點類。

 

2. 合併

/* 
 * 合併"左傾堆x"和"左傾堆y"
 *
 * 返回值:
 *     合併獲得的樹的根節點
 */
LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y)
{
    if(x == NULL)
        return y;
    if(y == NULL)
        return x;

    // 合併x和y時,將x做爲合併後的樹的根;
    // 這裏的操做是保證: x的key < y的key
    if(x->key > y->key)
        swap_leftist_node(x, y);

    // 將x的右孩子和y合併,"合併後的樹的根"是x的右孩子。
    x->right = merge_leftist(x->right, y);

    // 若是"x的左孩子爲空" 或者 "x的左孩子的npl<右孩子的npl"
    // 則,交換x和y
    if(x->left == NULL || x->left->npl < x->right->npl)
    {
        LeftistNode *tmp = x->left;
        x->left = x->right;
        x->right = tmp;
    }
    // 設置合併後的新樹(x)的npl
    if (x->right == NULL || x->left == NULL)
        x->npl = 0;
    else
        x->npl = (x->left->npl > x->right->npl) ? (x->right->npl + 1) : (x->left->npl + 1);

    return x;
}

merge_leftist(x, y)的做用是合併x和y這兩個左傾堆,並返回獲得的新堆。merge_leftist(x, y)是遞歸實現的。


3. 添加

/* 
 * 新建結點(key),並將其插入到左傾堆中
 *
 * 參數說明:
 *     heap 左傾堆的根結點
 *     key 插入結點的鍵值
 * 返回值:
 *     根節點
 */
LeftistNode* insert_leftist(LeftistHeap heap, Type key)
{
    LeftistNode *node;    // 新建結點

    // 若是新建結點失敗,則返回。
    if ((node = (LeftistNode *)malloc(sizeof(LeftistNode))) == NULL)
        return heap;
    node->key = key;
    node->npl = 0;
    node->left = node->right = NULL;

    return merge_leftist(heap, node);
}

insert_leftist(heap, key)的做用是新建鍵值爲key的結點,並將其插入到左傾堆中,並返回堆的根節點。

 

4. 刪除

/* 
 * 取出根節點
 *
 * 返回值:
 *     取出根節點後的新樹的根節點
 */
LeftistNode* delete_leftist(LeftistHeap heap)
{
    if (heap == NULL)
        return NULL;

    LeftistNode *l = heap->left;
    LeftistNode *r = heap->right;

    // 刪除根節點
    free(heap);

    return merge_leftist(l, r); // 返回左右子樹合併後的新樹
}

delete_leftist(heap)的做用是刪除左傾堆的最小節點,並返回刪除節點後的左傾堆根節點。

 

注意關於左傾堆的"前序遍歷"、"中序遍歷"、"後序遍歷"、"打印"、"銷燬"等接口就再也不單獨介紹了。後文的源碼中有給出它們的實現代碼,Please RTFSC(Read The Fucking Source Code)!

 

左傾堆的C實現(完整源碼)

左傾堆的頭文件(leftist.h)

 1 #ifndef _LEFTIST_TREE_H_
 2 #define _LEFTIST_TREE_H_
 3 
 4 typedef int Type;
 5 
 6 typedef struct _LeftistNode{
 7     Type   key;                    // 關鍵字(鍵值)
 8     int npl;                    // 零路經長度(Null Path Length)
 9     struct _LeftistNode *left;    // 左孩子
10     struct _LeftistNode *right;    // 右孩子
11 }LeftistNode, *LeftistHeap;
12 
13 // 前序遍歷"左傾堆"
14 void preorder_leftist(LeftistHeap heap);
15 // 中序遍歷"左傾堆"
16 void inorder_leftist(LeftistHeap heap);
17 // 後序遍歷"左傾堆"
18 void postorder_leftist(LeftistHeap heap);
19 
20 // 獲取最小值(保存到pval中),成功返回0,失敗返回-1。
21 int leftist_minimum(LeftistHeap heap, int *pval);
22 // 合併"左傾堆x"和"左傾堆y",並返回合併後的新樹
23 LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y);
24 // 將結點插入到左傾堆中,並返回根節點
25 LeftistNode* insert_leftist(LeftistHeap heap, Type key);
26 // 刪除結點(key爲節點的值),並返回根節點
27 LeftistNode* delete_leftist(LeftistHeap heap);
28 
29 // 銷燬左傾堆
30 void destroy_leftist(LeftistHeap heap);
31 
32 // 打印左傾堆
33 void print_leftist(LeftistHeap heap);
34 
35 #endif
View Code

左傾堆的實現文件(leftist.c)

  1 /**
  2  * C語言實現的左傾堆
  3  *
  4  * @author skywang
  5  * @date 2014/03/31
  6  */
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include "leftist.h"
 11 
 12 /*
 13  * 前序遍歷"左傾堆"
 14  */
 15 void preorder_leftist(LeftistHeap heap)
 16 {
 17     if(heap != NULL)
 18     {
 19         printf("%d ", heap->key);
 20         preorder_leftist(heap->left);
 21         preorder_leftist(heap->right);
 22     }
 23 }
 24 
 25 /*
 26  * 中序遍歷"左傾堆"
 27  */
 28 void inorder_leftist(LeftistHeap heap)
 29 {
 30     if(heap != NULL)
 31     {
 32         inorder_leftist(heap->left);
 33         printf("%d ", heap->key);
 34         inorder_leftist(heap->right);
 35     }
 36 }
 37 
 38 /*
 39  * 後序遍歷"左傾堆"
 40  */
 41 void postorder_leftist(LeftistHeap heap)
 42 {
 43     if(heap != NULL)
 44     {
 45         postorder_leftist(heap->left);
 46         postorder_leftist(heap->right);
 47         printf("%d ", heap->key);
 48     }
 49 }
 50 
 51 /*
 52  * 交換兩個節點的內容
 53  */
 54 static void swap_leftist_node(LeftistNode *x, LeftistNode *y)
 55 {
 56     LeftistNode tmp = *x;
 57     *x = *y;
 58     *y = tmp;
 59 }
 60 
 61 /* 
 62  * 獲取最小值
 63  *
 64  * 返回值:
 65  *    成功返回0,失敗返回-1
 66  */
 67 int leftist_minimum(LeftistHeap heap, int *pval)
 68 {
 69     if (heap == NULL)
 70         return -1;
 71 
 72     *pval = heap->key;
 73 
 74     return 0;
 75 }
 76  
 77 /* 
 78  * 合併"左傾堆x"和"左傾堆y"
 79  *
 80  * 返回值:
 81  *     合併獲得的樹的根節點
 82  */
 83 LeftistNode* merge_leftist(LeftistHeap x, LeftistHeap y)
 84 {
 85     if(x == NULL)
 86         return y;
 87     if(y == NULL)
 88         return x;
 89 
 90     // 合併x和y時,將x做爲合併後的樹的根;
 91     // 這裏的操做是保證: x的key < y的key
 92     if(x->key > y->key)
 93         swap_leftist_node(x, y);
 94 
 95     // 將x的右孩子和y合併,"合併後的樹的根"是x的右孩子。
 96     x->right = merge_leftist(x->right, y);
 97 
 98     // 若是"x的左孩子爲空" 或者 "x的左孩子的npl<右孩子的npl"
 99     // 則,交換x和y
100     if(x->left == NULL || x->left->npl < x->right->npl)
101     {
102         LeftistNode *tmp = x->left;
103         x->left = x->right;
104         x->right = tmp;
105     }
106     // 設置合併後的新樹(x)的npl
107     if (x->right == NULL || x->left == NULL)
108         x->npl = 0;
109     else
110         x->npl = (x->left->npl > x->right->npl) ? (x->right->npl + 1) : (x->left->npl + 1);
111 
112     return x;
113 }
114 
115 /* 
116  * 新建結點(key),並將其插入到左傾堆中
117  *
118  * 參數說明:
119  *     heap 左傾堆的根結點
120  *     key 插入結點的鍵值
121  * 返回值:
122  *     根節點
123  */
124 LeftistNode* insert_leftist(LeftistHeap heap, Type key)
125 {
126     LeftistNode *node;    // 新建結點
127 
128     // 若是新建結點失敗,則返回。
129     if ((node = (LeftistNode *)malloc(sizeof(LeftistNode))) == NULL)
130         return heap;
131     node->key = key;
132     node->npl = 0;
133     node->left = node->right = NULL;
134 
135     return merge_leftist(heap, node);
136 }
137 
138 /* 
139  * 取出根節點
140  *
141  * 返回值:
142  *     取出根節點後的新樹的根節點
143  */
144 LeftistNode* delete_leftist(LeftistHeap heap)
145 {
146     if (heap == NULL)
147         return NULL;
148 
149     LeftistNode *l = heap->left;
150     LeftistNode *r = heap->right;
151 
152     // 刪除根節點
153     free(heap);
154 
155     return merge_leftist(l, r); // 返回左右子樹合併後的新樹
156 }
157 
158 /*
159  * 銷燬左傾堆
160  */
161 void destroy_leftist(LeftistHeap heap)
162 {
163     if (heap==NULL)
164         return ;
165 
166     if (heap->left != NULL)
167         destroy_leftist(heap->left);
168     if (heap->right != NULL)
169         destroy_leftist(heap->right);
170 
171     free(heap);
172 }
173 
174 /*
175  * 打印"左傾堆"
176  *
177  * heap       -- 左傾堆的節點
178  * key        -- 節點的鍵值 
179  * direction  --  0,表示該節點是根節點;
180  *               -1,表示該節點是它的父結點的左孩子;
181  *                1,表示該節點是它的父結點的右孩子。
182  */
183 static void leftist_print(LeftistHeap heap, Type key, int direction)
184 {
185     if(heap != NULL)
186     {
187         if(direction==0)    // heap是根節點
188             printf("%2d(%d) is root\n", heap->key, heap->npl);
189         else                // heap是分支節點
190             printf("%2d(%d) is %2d's %6s child\n", heap->key, heap->npl, key, direction==1?"right" : "left");
191 
192         leftist_print(heap->left, heap->key, -1);
193         leftist_print(heap->right,heap->key,  1);
194     }
195 }
196 
197 void print_leftist(LeftistHeap heap)
198 {
199     if (heap != NULL)
200         leftist_print(heap, heap->key,  0);
201 }
View Code

左傾堆的測試程序(leftist_test.c)

 1 /**
 2  * C語言實現的左傾堆
 3  *
 4  * @author skywang
 5  * @date 2014/03/31
 6  */
 7 
 8 #include <stdio.h>
 9 #include "leftist.h"
10 
11 #define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) )
12 
13 void main()
14 {
15     int i;
16     int a[]= {10,40,24,30,36,20,12,16};
17     int b[]= {17,13,11,15,19,21,23};
18     int alen=LENGTH(a);
19     int blen=LENGTH(b);
20     LeftistHeap ha,hb;
21 
22     ha=hb=NULL;
23 
24     printf("== 左傾堆(ha)中依次添加: ");
25     for(i=0; i<alen; i++)
26     {
27         printf("%d ", a[i]);
28         ha = insert_leftist(ha, a[i]);
29     }
30     printf("\n== 左傾堆(ha)的詳細信息: \n");
31     print_leftist(ha);
32 
33 
34     printf("\n== 左傾堆(hb)中依次添加: ");
35     for(i=0; i<blen; i++)
36     {
37         printf("%d ", b[i]);
38         hb = insert_leftist(hb, b[i]);
39     }
40     printf("\n== 左傾堆(hb)的詳細信息: \n");
41     print_leftist(hb);
42 
43     // 將"左傾堆hb"合併到"左傾堆ha"中。
44     ha = merge_leftist(ha, hb);
45     printf("\n== 合併ha和hb後的詳細信息: \n");
46     print_leftist(ha);
47 
48 
49     // 銷燬左傾堆
50     destroy_leftist(ha);
51 }
View Code

 

左傾堆的C測試程序

左傾堆的測試程序已經包含在它的實現文件(leftist_test.c)中了,這裏僅給出它的運行結果:

== 左傾堆(ha)中依次添加: 10 40 24 30 36 20 12 16 
== 左傾堆(ha)的詳細信息: 
10(2) is root
24(1) is 10's   left child
30(0) is 24's   left child
36(0) is 24's  right child
12(1) is 10's  right child
20(0) is 12's   left child
40(0) is 20's   left child
16(0) is 12's  right child

== 左傾堆(hb)中依次添加: 17 13 11 15 19 21 23 
== 左傾堆(hb)的詳細信息: 
11(2) is root
15(1) is 11's   left child
19(0) is 15's   left child
21(0) is 15's  right child
13(1) is 11's  right child
17(0) is 13's   left child
23(0) is 13's  right child

== 合併ha和hb後的詳細信息: 
10(2) is root
11(2) is 10's   left child
15(1) is 11's   left child
19(0) is 15's   left child
21(0) is 15's  right child
12(1) is 11's  right child
13(1) is 12's   left child
17(0) is 13's   left child
16(0) is 13's  right child
23(0) is 16's   left child
20(0) is 12's  right child
40(0) is 20's   left child
24(1) is 10's  right child
30(0) is 24's   left child
36(0) is 24's  right child
相關文章
相關標籤/搜索