概要
本章介紹斜堆。和以往一樣,本文會先對斜堆的理論知識進行簡單介紹,然后給出C語言的實現。后續再分別給出C++和Java版本的實現;實現的語言雖不同,但是原理如出一轍,選擇其中之一進行了解即可。若文章有錯誤或不足的地方,請不吝指出!
目錄
1. 斜堆的介紹
2. 斜堆的基本操作
3. 斜堆的C實現(完整源碼)
4. 斜堆的C測試程序
轉載請注明出處:http://www.cnblogs.com/skywang12345/p/3638493.html
更多內容:數據結構與算法系列 目錄
斜堆的介紹
斜堆(Skew heap)也叫自適應堆(self-adjusting heap),它是左傾堆的一個變種。和左傾堆一樣,它通常也用於實現優先隊列。它的合並操作的時間復雜度也是O(lg n)。
相比於左傾堆,斜堆的節點沒有"零距離"這個屬性。除此之外,它們斜堆的合並操作也不同。斜堆的合並操作算法如下:
(01) 如果一個空斜堆與一個非空斜堆合並,返回非空斜堆。
(02) 如果兩個斜堆都非空,那么比較兩個根節點,取較小堆的根節點為新的根節點。將"較小堆的根節點的右孩子"和"較大堆"進行合並。
(03) 合並后,交換新堆根節點的左孩子和右孩子。
第(03)步是斜堆和左傾堆的合並操作差別的關鍵所在,如果是左傾堆,則合並后要比較左右孩子的零距離大小,若右孩子的零距離 > 左孩子的零距離,則交換左右孩子;最后,在設置根的零距離。
斜堆的基本操作
1. 頭文件
#ifndef _SKEW_HEAP_H_ #define _SKEW_HEAP_H_ typedef int Type; typedef struct _SkewNode{ Type key; // 關鍵字(鍵值) struct _SkewNode *left; // 左孩子 struct _SkewNode *right; // 右孩子 }SkewNode, *SkewHeap; // 前序遍歷"斜堆" void preorder_skewheap(SkewHeap heap); // 中序遍歷"斜堆" void inorder_skewheap(SkewHeap heap); // 后序遍歷"斜堆" void postorder_skewheap(SkewHeap heap); // 獲取最小值(保存到pval中),成功返回0,失敗返回-1。 int skewheap_minimum(SkewHeap heap, int *pval); // 合並"斜堆x"和"斜堆y",並返回合並后的新樹 SkewNode* merge_skewheap(SkewHeap x, SkewHeap y); // 將結點插入到斜堆中,並返回根節點 SkewNode* insert_skewheap(SkewHeap heap, Type key); // 刪除結點(key為節點的值),並返回根節點 SkewNode* delete_skewheap(SkewHeap heap); // 銷毀斜堆 void destroy_skewheap(SkewHeap heap); // 打印斜堆 void print_skewheap(SkewHeap heap); #endif
SkewNode是斜堆對應的節點類。
2. 合並
/* * 合並"斜堆x"和"斜堆y" * * 返回值: * 合並得到的樹的根節點 */ SkewNode* merge_skewheap(SkewHeap x, SkewHeap y) { if(x == NULL) return y; if(y == NULL) return x; // 合並x和y時,將x作為合並后的樹的根; // 這里的操作是保證: x的key < y的key if(x->key > y->key) swap_skewheap_node(x, y); // 將x的右孩子和y合並, // 合並后直接交換x的左右孩子,而不需要像左傾堆一樣考慮它們的npl。 SkewNode *tmp = merge_skewheap(x->right, y); x->right = x->left; x->left = tmp; return x; }
merge_skewheap(x, y)的作用是合並x和y這兩個斜堆,並返回得到的新堆。merge_skewheap(x, y)是遞歸實現的。
3. 添加
/* * 新建結點(key),並將其插入到斜堆中 * * 參數說明: * heap 斜堆的根結點 * key 插入結點的鍵值 * 返回值: * 根節點 */ SkewNode* insert_skewheap(SkewHeap heap, Type key) { SkewNode *node; // 新建結點 // 如果新建結點失敗,則返回。 if ((node = (SkewNode *)malloc(sizeof(SkewNode))) == NULL) return heap; node->key = key; node->left = node->right = NULL; return merge_skewheap(heap, node); }
insert_skewheap(heap, key)的作用是新建鍵值為key的結點,並將其插入到斜堆中,並返回堆的根節點。
4. 刪除
/* * 取出根節點 * * 返回值: * 取出根節點后的新樹的根節點 */ SkewNode* delete_skewheap(SkewHeap heap) { SkewNode *l = heap->left; SkewNode *r = heap->right; // 刪除根節點 free(heap); return merge_skewheap(l, r); // 返回左右子樹合並后的新樹 }
delete_skewheap(heap)的作用是刪除斜堆的最小節點,並返回刪除節點后的斜堆根節點。
注意:關於斜堆的"前序遍歷"、"中序遍歷"、"后序遍歷"、"打印"、"銷毀"等接口就不再單獨介紹了。后文的源碼中有給出它們的實現代碼,Please RTFSC(Read The Fucking Source Code)!
斜堆的C實現(完整源碼)
斜堆的頭文件(skewheap.h)

1 #ifndef _SKEW_HEAP_H_ 2 #define _SKEW_HEAP_H_ 3 4 typedef int Type; 5 6 typedef struct _SkewNode{ 7 Type key; // 關鍵字(鍵值) 8 struct _SkewNode *left; // 左孩子 9 struct _SkewNode *right; // 右孩子 10 }SkewNode, *SkewHeap; 11 12 // 前序遍歷"斜堆" 13 void preorder_skewheap(SkewHeap heap); 14 // 中序遍歷"斜堆" 15 void inorder_skewheap(SkewHeap heap); 16 // 后序遍歷"斜堆" 17 void postorder_skewheap(SkewHeap heap); 18 19 // 獲取最小值(保存到pval中),成功返回0,失敗返回-1。 20 int skewheap_minimum(SkewHeap heap, int *pval); 21 // 合並"斜堆x"和"斜堆y",並返回合並后的新樹 22 SkewNode* merge_skewheap(SkewHeap x, SkewHeap y); 23 // 將結點插入到斜堆中,並返回根節點 24 SkewNode* insert_skewheap(SkewHeap heap, Type key); 25 // 刪除結點(key為節點的值),並返回根節點 26 SkewNode* delete_skewheap(SkewHeap heap); 27 28 // 銷毀斜堆 29 void destroy_skewheap(SkewHeap heap); 30 31 // 打印斜堆 32 void print_skewheap(SkewHeap heap); 33 34 #endif
斜堆的實現文件(skewheap.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 "skewheap.h" 11 12 /* 13 * 前序遍歷"斜堆" 14 */ 15 void preorder_skewheap(SkewHeap heap) 16 { 17 if(heap != NULL) 18 { 19 printf("%d ", heap->key); 20 preorder_skewheap(heap->left); 21 preorder_skewheap(heap->right); 22 } 23 } 24 25 /* 26 * 中序遍歷"斜堆" 27 */ 28 void inorder_skewheap(SkewHeap heap) 29 { 30 if(heap != NULL) 31 { 32 inorder_skewheap(heap->left); 33 printf("%d ", heap->key); 34 inorder_skewheap(heap->right); 35 } 36 } 37 38 /* 39 * 后序遍歷"斜堆" 40 */ 41 void postorder_skewheap(SkewHeap heap) 42 { 43 if(heap != NULL) 44 { 45 postorder_skewheap(heap->left); 46 postorder_skewheap(heap->right); 47 printf("%d ", heap->key); 48 } 49 } 50 51 /* 52 * 交換兩個節點的內容 53 */ 54 static void swap_skewheap_node(SkewNode *x, SkewNode *y) 55 { 56 SkewNode tmp = *x; 57 *x = *y; 58 *y = tmp; 59 } 60 61 /* 62 * 獲取最小值 63 * 64 * 返回值: 65 * 成功返回0,失敗返回-1 66 */ 67 int skewheap_minimum(SkewHeap 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 SkewNode* merge_skewheap(SkewHeap x, SkewHeap 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_skewheap_node(x, y); 94 95 // 將x的右孩子和y合並, 96 // 合並后直接交換x的左右孩子,而不需要像左傾堆一樣考慮它們的npl。 97 SkewNode *tmp = merge_skewheap(x->right, y); 98 x->right = x->left; 99 x->left = tmp; 100 101 return x; 102 } 103 104 /* 105 * 新建結點(key),並將其插入到斜堆中 106 * 107 * 參數說明: 108 * heap 斜堆的根結點 109 * key 插入結點的鍵值 110 * 返回值: 111 * 根節點 112 */ 113 SkewNode* insert_skewheap(SkewHeap heap, Type key) 114 { 115 SkewNode *node; // 新建結點 116 117 // 如果新建結點失敗,則返回。 118 if ((node = (SkewNode *)malloc(sizeof(SkewNode))) == NULL) 119 return heap; 120 node->key = key; 121 node->left = node->right = NULL; 122 123 return merge_skewheap(heap, node); 124 } 125 126 /* 127 * 取出根節點 128 * 129 * 返回值: 130 * 取出根節點后的新樹的根節點 131 */ 132 SkewNode* delete_skewheap(SkewHeap heap) 133 { 134 SkewNode *l = heap->left; 135 SkewNode *r = heap->right; 136 137 // 刪除根節點 138 free(heap); 139 140 return merge_skewheap(l, r); // 返回左右子樹合並后的新樹 141 } 142 143 /* 144 * 銷毀斜堆 145 */ 146 void destroy_skewheap(SkewHeap heap) 147 { 148 if (heap==NULL) 149 return ; 150 151 if (heap->left != NULL) 152 destroy_skewheap(heap->left); 153 if (heap->right != NULL) 154 destroy_skewheap(heap->right); 155 156 free(heap); 157 } 158 159 /* 160 * 打印"斜堆" 161 * 162 * heap -- 斜堆的節點 163 * key -- 節點的鍵值 164 * direction -- 0,表示該節點是根節點; 165 * -1,表示該節點是它的父結點的左孩子; 166 * 1,表示該節點是它的父結點的右孩子。 167 */ 168 static void skewheap_print(SkewHeap heap, Type key, int direction) 169 { 170 if(heap != NULL) 171 { 172 if(direction==0) // heap是根節點 173 printf("%2d is root\n", heap->key); 174 else // heap是分支節點 175 printf("%2d is %2d's %6s child\n", heap->key, key, direction==1?"right" : "left"); 176 177 skewheap_print(heap->left, heap->key, -1); 178 skewheap_print(heap->right,heap->key, 1); 179 } 180 } 181 182 void print_skewheap(SkewHeap heap) 183 { 184 if (heap != NULL) 185 skewheap_print(heap, heap->key, 0); 186 }
斜堆的測試程序(skewheap_test.c)

1 /** 2 * C語言實現的斜堆 3 * 4 * @author skywang 5 * @date 2014/03/31 6 */ 7 8 #include <stdio.h> 9 #include "skewheap.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 SkewHeap 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_skewheap(ha, a[i]); 29 } 30 printf("\n== 斜堆(ha)的詳細信息: \n"); 31 print_skewheap(ha); 32 33 34 printf("\n== 斜堆(hb)中依次添加: "); 35 for(i=0; i<blen; i++) 36 { 37 printf("%d ", b[i]); 38 hb = insert_skewheap(hb, b[i]); 39 } 40 printf("\n== 斜堆(hb)的詳細信息: \n"); 41 print_skewheap(hb); 42 43 // 將"斜堆hb"合並到"斜堆ha"中。 44 ha = merge_skewheap(ha, hb); 45 printf("\n== 合並ha和hb后的詳細信息: \n"); 46 print_skewheap(ha); 47 48 49 // 銷毀斜堆 50 destroy_skewheap(ha); 51 }
斜堆的C測試程序
斜堆的測試程序已經包含在它的實現文件(skewheap_test.c)中了,這里僅給出它的運行結果:
== 斜堆(ha)中依次添加: 10 40 24 30 36 20 12 16 == 斜堆(ha)的詳細信息: 10 is root 16 is 10's left child 20 is 16's left child 30 is 20's left child 40 is 30's left child 12 is 10's right child 24 is 12's left child 36 is 24's left child == 斜堆(hb)中依次添加: 17 13 11 15 19 21 23 == 斜堆(hb)的詳細信息: 11 is root 13 is 11's left child 17 is 13's left child 23 is 17's left child 19 is 13's right child 15 is 11's right child 21 is 15's left child == 合並ha和hb后的詳細信息: 10 is root 11 is 10's left child 12 is 11's left child 15 is 12's left child 21 is 15's left child 24 is 12's right child 36 is 24's left child 13 is 11's right child 17 is 13's left child 23 is 17's left child 19 is 13's right child 16 is 10's right child 20 is 16's left child 30 is 20's left child 40 is 30's left child