二叉查詢樹(查詢、插入、刪除)——C語言

藍海人發表於2019-08-07

二叉查詢樹

二叉查詢樹(BST:Binary Search Tree)是一種特殊的二叉樹,它改善了二叉樹節點查詢的效率。二叉查詢樹有以下性質:

(1)若左子樹不空,則左子樹上所有節點的值均小於它的根節點的值
(2)若右子樹不空,則右子樹上所有節點的值均大於它的根節點的值
(3)左、右子樹也分別為二叉排序樹
(4)沒有鍵值相等的節點

 

二叉查詢樹節點的定義:

1 typedef struct BSTreeNode
2 {
3     int data;
4     struct BSTreeNode *left;//左子樹
5     struct BSTreeNode *right;//右子樹
6 }BSTree;

跟普通二叉樹的節點定義相同

因為查詢節點和插入節點都是在已經構建好二叉查詢樹的前提下才能進行的,在刪除節點的時候才涉及到調整二叉樹的操作,所以這裡先以前序遍歷的順序直接輸入一個二叉查詢樹,程式碼如下

 1 /* 建立二叉查詢樹(資料以前序遍歷順序輸入)*/
 2 BSTree *Create_BSTreeNode(BSTree *nod)
 3 {
 4     int num;
 5 
 6     scanf_s("%d", &num, 1);
 7     if (num == 0)    /* 假定輸入的資料都為正數,以0作為NULL的標誌 */
 8     {
 9         return NULL;
10     }
11     else
12     {
13         if ((nod = (BSTree *)malloc(sizeof(BSTree))) == NULL)
14         {
15             printf("記憶體空間不足");
16             exit(0);
17         }
18         nod->data = num;
19         nod->left = Create_BSTreeNode(nod->left);
20         nod->right = Create_BSTreeNode(nod->right);
21 
22         return nod;
23     }
24 }
25 
26 /* 前序遍歷二叉樹,並列印 */
27 void PreOrder_Traverse(BSTree *nod, int level)
28 {
29     if (nod == NULL)
30     {
31         return ;
32     }
33 
34     printf("data = %d level = %d\n", nod->data, level);
35     PreOrder_Traverse(nod->left, level + 1);
36     PreOrder_Traverse(nod->right, level + 1);
37 }
View Code

 

1、查詢節點(遞迴實現)

若根結點的關鍵字等於查詢的關鍵字,查詢成功,若小於根結點的關鍵字的值,遞迴查詢左子樹,若大於根結點的關鍵字的值,遞迴查詢右子樹,若子樹為空,則查詢失敗,查詢的操作較為簡單,實現程式碼如下

 1 /* 查詢特定值 */
 2 void SearchData(int targ, BSTree *nod)
 3 {
 4     if (nod != NULL)
 5     {
 6         if (nod->data == targ)
 7         {
 8             printf("查詢值存在,值為%d\n", nod->data);
 9         }
10         else if (nod->data > targ)
11         {
12             SearchData(targ, nod->left);    //遞迴查詢左子樹
13         }
14         else if (nod->data < targ)
15         {
16             SearchData(targ, nod->right);    //遞迴查詢右子樹
17         }
18     }
19     else if (nod == NULL)
20     {
21         printf("查詢值不存在\n");
22     }
23 }

通過 BST 查詢節點,理想情況下我們需要檢查的節點數可以減半。如下圖中的 BST 樹,包含了 15 個節點。從根節點開始執行查詢演算法,第一次比較決定我們是移向左子樹還是右子樹,對於任意一種情況,一旦執行這一步,我們需要訪問的節點數就減少了一半,從 15 降到了 7。同樣,下一步訪問的節點也減少了一半,從 7 降到了 3,以此類推,根據這一特點,查詢演算法的時間複雜度應該是 O(log­2n)

 

 

(圖片來源:https://www.cnblogs.com/gaochundong/p/binary_search_tree.html)

 

對於 BST 查詢演算法來說,其十分依賴於樹中節點的拓撲結構,也就是節點間的佈局關係,當 BST 樹中的節點以扇形結構散開時,對它的插入、刪除和查詢操作最優的情況下可以達到亞線性的執行時間 O(log2n),

因為當在 BST 中查詢一個節點時,每一步比較操作後都會將節點的數量減少一半。儘管如此,如果拓撲結構像下圖中的樣子時,執行時間就會退減到線性時間 O(n)。因為每一步比較操作後還是需要逐個比較其餘的節點,

也就是說,在這種情況下,在 BST 中查詢節點與在陣列(Array)中查詢就基本類似了。

因此,BST 演算法查詢時間依賴於樹的拓撲結構。最佳情況是 O(log­2n),而最壞情況是 O(n)

測試用例:

 

以前序遍歷順序輸入二叉查詢樹

 

 

 

2、插入節點(遞迴實現)

新插入的結點一定是一個新新增的葉子結點,如下圖

 

雖然上面兩種插入結果得到的二叉樹都符合二叉查詢樹的性質,但是不滿足“新插入的結點一定是一個新新增的葉子結點因為有了這個特點插入的操作變得相對簡單,實現程式碼如下

 1 /* 新增新節點 */
 2 BSTree *AddNewNode(BSTree *cur, int NewData)
 3 {
 4     if (cur == NULL)
 5     {
 6         if ((cur = (BSTree *)malloc(sizeof(BSTree))) == NULL)    //建立新節點
 7         {
 8             printf("記憶體不足");
 9             exit(0);
10         }
11         cur->data = NewData;
12         cur->left = NULL;
13         cur->right = NULL;
14 
15         return cur;
16     }
17     if (NewData > cur->data)
18     {
19         cur->right = AddNewNode(cur->right, NewData);
20     }
21     else if (NewData < cur->data)
22     {
23         cur->left = AddNewNode(cur->left, NewData);
24     }
25     else if (NewData == cur->data)
26     {
27         printf("不允許插入重複值\n");
28         exit(0);
29     }
30 
31     return cur;
32 }

 

實際執行效果(測試查詢與插入):

查詢:以前序遍歷順序輸入一個二叉查詢樹(0作為NULL標誌)

插入:以前序遍歷順序輸入一個二叉查詢樹

 

3、刪除節點

刪除節點的操作相對查詢和插入要相對複雜一些,主要考慮以下三種情況(前兩種情況操作較為簡單,第三種較為複雜

在刪除操作前先用要找到待刪除節點的位置(這裡使用的遞迴,也可以改成迭代)

 

情形一:刪除葉子節點

 

因為刪除葉子節點不會破壞BST的結構,刪除葉子節點的操作較為簡單,步驟如下

1、判斷待刪除節點的左右子樹是否為空,如果都為空那麼就是葉子節點

2、判斷待刪除節點是待刪除節點父節點的右子樹還是左子樹,將對應的指標賦值NULL

3、free待刪除節點

 

實現程式碼:

 1 /* 刪除節點 */
 2 void DeletNode(BSTree *parent, BSTree *cur, int DelData)
 3 {
 4     BSTree *SNode = NULL; //後繼節點
 5     BSTree *PSNode = NULL;    //後繼節點的父節點
 6 
 7     if (DelData > cur->data)
 8     {
 9         DeletNode(cur, cur->right, DelData);
10     }
11     else if (DelData < cur->data)
12     {
13         DeletNode(cur, cur->left, DelData);
14     }
15     else if(DelData == cur->data)
16     {
17         if (cur->left == NULL && cur->right == NULL)    //刪除節點為葉子節點
18         {
19             if (parent->left == cur)    //如果該節點是父節點的左子樹
20             {
21                 parent->left = NULL;
22             }
23             else if (parent->right == cur)    //如果該節點是父節點的右子樹
24             {
25                 parent->right = NULL;
26             }
27             free(cur);    //釋放刪除節點
28         }

 

情形二:刪除帶有一個子節點的節點

(圖片來源:https://www.cnblogs.com/songwenjie/p/8973217.html)

上圖寫了四種,但對待刪除節點來說只有兩種,只有左子樹,或只有右子樹,兩種情況的處理方式基本相同,都是將待刪除節點的左/右子樹 賦值給 待刪除節點的父節點的左/右子樹

 

實現程式碼:

 1 else if(cur->left != NULL && cur->right == NULL)    //待刪除節點只有左子樹
 2         {
 3             if (parent->left == cur)
 4             {
 5                 parent->left = cur->left;
 6             }
 7             else if (parent->right == cur)
 8             {
 9                 parent->right = cur->left;
10             }
11             free(cur);    //釋放待刪除節點
12         }
13         else if(cur->left == NULL && cur->right != NULL)    //待刪除節點只有右子樹
14         {
15             if (parent->left == cur)
16             {
17                 parent->left = cur->right;
18             }
19             else if (parent->right == cur)
20             {
21                 parent->right = cur->right;
22             }
23             free(cur);    //釋放待刪除節點
24         }

 

情形三:刪除帶兩個節點的節點

因為刪除節點會有破壞 BST 正確結構的風險,刪除帶兩個節點的節點操作顯得較為複雜,首先需要找到待刪除節點的 後繼節點 和 該後繼節點的父節點,(一個節點的後繼節點是指,這個節點在中序遍歷序列中的下一個節點,相應的,前驅節點是指這個節點在中序遍歷序列中的上一個節點),刪除節點的後繼節點一定是刪除節點右子樹的最左側節點,這篇隨筆採用的方式是後繼節點替代待刪除節點的方式而不是前驅節點替代刪除節點,需要考慮的情況如下

1、後繼節點為待刪除節點的子節點

在後繼節點為待刪除節點的子節點的前提下,該後繼節點有右子樹和沒有右子樹的操作是相同的,都是將 後繼節點 替代 待刪除節點,並將待刪除節點的左子樹 賦值給 後繼節點的左子樹

 

 實現程式碼:

 1 else if(cur->left != NULL && cur->right != NULL)    //待刪除節點既有左子樹也有右子樹
 2         {
 3             SNode = SearchSuccessorNode(cur->right);    //搜尋後繼節點
 4             PSNode = SearchParentofSNode(cur->right, cur->right);    //搜尋後繼節點的父節點
 5 
 6             if (cur->right == SNode)    //後繼節點為待刪除節點的右子樹(後繼節點有右子樹和沒有右子樹的操作相同)
 7             {
 8                 if (parent->left == cur)
 9                 {
10                     parent->left = SNode;
11                     SNode->left = cur->left;
12 
13                     free(cur);
14                 }
15                 else if (parent->right == cur)
16                 {
17                     parent->right = SNode;
18                     SNode->left = cur->left;
19 
20                     free(cur);
21                 }
22             }

 

2、後繼節點不為待刪除節點的子節點

這裡後繼節點還要在分為後繼節點有子節點沒有子節點的情況

(1)後繼節點沒有右子節點

 

根據實現程式碼來標註上面的節點

刪除後:

實現程式碼:

 1 else if (cur->right != SNode && SNode->right == NULL)    //後繼節點不為待刪除節點的右子樹,並且該後繼節點沒有右子樹
 2             {
 3                 if (parent->left == cur)
 4                 {
 5                     parent->left = SNode;
 6                     SNode->left = cur->left;
 7                     SNode->right = cur->right;
 8                     PSNode->left = NULL;
 9 
10                     free(cur);
11                 }
12                 else if (parent->right == cur)
13                 {
14                     parent->right = SNode;
15                     SNode->left = cur->left;
16                     SNode->right = cur->right;
17                     PSNode->left = NULL;
18 
19                     free(cur);
20                 }
21             }

 

(2)後繼節點有右子節點

 

刪除後:

與上面的後繼節點沒有右子節點相比需要增加一個操作,需要將後繼節點的右子樹 賦值給 後繼節點的父節點的左子樹

實現程式碼: 

 1 else if (cur->right != SNode && SNode->right != NULL)    //後繼節點不為待刪除節點的右子樹,並且該後繼節點有右子樹
 2             {
 3                 if (parent->left == cur)
 4                 {
 5                     parent->left = SNode;
 6                     PSNode->left = SNode->right;    //後繼節點的右子樹作為後繼節點父節點的左子樹
 7                     SNode->left = cur->left;
 8                     SNode->right = cur->right;
 9 
10                     free(cur);
11                 }
12                 else if (parent->right == cur)
13                 {
14                     parent->right = SNode;
15                     PSNode->left = SNode->right;    //後繼節點的右子樹作為後繼節點父節點的左子樹
16                     SNode->left = cur->left;
17                     SNode->right = cur->right;
18 
19                     free(cur);
20                 }
21             }

 

測試資料:

一、“後繼節點是刪除節點的子節點”(因為後繼節點有無子樹的操作相同,這裡只測試沒有子樹的情況)

 

二、“後繼節點不是刪除節點的子節點,且後繼節點沒有右子樹”

 

 

三、“後繼節點不是刪除節點的子節點,且後繼節點有右子樹”

 

 

完整程式碼:(注:對free(cur)的位置進行了調整)

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <conio.h>
  4 
  5 #define LEFT 1
  6 #define RIGHT 2
  7 
  8 typedef struct BSTreeNode
  9 {
 10     int data;
 11     struct BSTreeNode *left;//左子樹
 12     struct BSTreeNode *right;//右子樹
 13 }BSTree;
 14 
 15 BSTree *Create_BSTreeNode(BSTree *nod);    //建立二叉查詢樹
 16 void PreOrder_Traverse(BSTree *nod, int level);    //前序遍歷二叉樹,並列印
 17 void SearchData(int targ, BSTree *nod);    //查詢特定值
 18 BSTree *AddNewNode(BSTree *cur, int NewData);    //新增新的節點
 19 void DeletNode(BSTree *parent, BSTree *cur, int DelData);    //刪除節點
 20 BSTree *SearchSuccessorNode(BSTree *nod);    //搜尋後繼節點
 21 BSTree *SearchParentofSNode(BSTree *Pnod, BSTree *nod);    //搜尋後繼節點的父節點
 22 
 23 int main()
 24 {
 25     BSTree *nod = NULL;
 26     //int num;
 27     //int key;
 28     int del;
 29 
 30     nod = Create_BSTreeNode(nod);
 31     PreOrder_Traverse(nod, 1);
 32     //printf("輸出查詢資料\n");
 33     //scanf_s("%d", &num, 1);
 34     //SearchData(num, nod);
 35     //printf("輸出新插入資料\n");
 36     //scanf_s("%d", &key, 1);
 37 
 38     printf("輸出刪除的資料\n");
 39     scanf_s("%d", &del, 1);
 40     DeletNode(nod, nod, del);
 41 
 42     //AddNewNode(nod, key);
 43     PreOrder_Traverse(nod, 1);
 44 
 45     return 0;
 46 }
 47 
 48 /* 搜尋後繼節點的父節點 */
 49 BSTree *SearchParentofSNode(BSTree *Pnod, BSTree *nod)
 50 {
 51     while (1)
 52     {
 53         if (nod->left != NULL)
 54         {
 55             Pnod = nod;
 56             nod = nod->left;
 57         }
 58         else
 59         {
 60             break;
 61         }
 62     }
 63 
 64     return Pnod;
 65 }
 66 
 67 /* 搜尋後繼節點 */
 68 BSTree *SearchSuccessorNode(BSTree *nod)
 69 {
 70     while (1)
 71     {
 72         if (nod->left != NULL)
 73         {
 74             nod = nod->left;
 75         }
 76         else
 77         {
 78             break;
 79         }
 80     }
 81 
 82     return nod;
 83 }
 84 
 85 /* 刪除節點 */
 86 /* cur為待刪除節點, parent為待刪除節點的父節點 */
 87 void DeletNode(BSTree *parent, BSTree *cur, int DelData)
 88 {
 89     BSTree *SNode = NULL; //後繼節點
 90     BSTree *PSNode = NULL;    //後繼節點的父節點
 91 
 92     if (DelData > cur->data)
 93     {
 94         DeletNode(cur, cur->right, DelData);
 95     }
 96     else if (DelData < cur->data)
 97     {
 98         DeletNode(cur, cur->left, DelData);
 99     }
100     else if(DelData == cur->data)
101     {
102         if (cur->left == NULL && cur->right == NULL)    //待刪除節點為葉子節點
103         {
104             if (parent->left == cur)    //如果該節點是父節點的左子樹
105             {
106                 parent->left = NULL;
107             }
108             else if (parent->right == cur)    //如果該節點是父節點的右子樹
109             {
110                 parent->right = NULL;
111             }
112         }
113         else if(cur->left != NULL && cur->right == NULL)    //待刪除節點只有左子樹
114         {
115             if (parent->left == cur)
116             {
117                 parent->left = cur->left;
118             }
119             else if (parent->right == cur)
120             {
121                 parent->right = cur->left;
122             }
123         }
124         else if(cur->left == NULL && cur->right != NULL)    //待刪除節點只有右子樹
125         {
126             if (parent->left == cur)
127             {
128                 parent->left = cur->right;
129             }
130             else if (parent->right == cur)
131             {
132                 parent->right = cur->right;
133             }
134         }
135         else if(cur->left != NULL && cur->right != NULL)    //待刪除節點既有左子樹也有右子樹
136         {
137             SNode = SearchSuccessorNode(cur->right);    //搜尋後繼節點
138             PSNode = SearchParentofSNode(cur->right, cur->right);    //搜尋後繼節點的父節點
139 
140             if (cur->right == SNode)    //後繼節點為待刪除節點的右子樹(後繼節點有右子樹和沒有右子樹的操作相同)
141             {
142                 if (parent->left == cur)
143                 {
144                     parent->left = SNode;
145                     SNode->left = cur->left;
146                 }
147                 else if (parent->right == cur)
148                 {
149                     parent->right = SNode;
150                     SNode->left = cur->left;
151                 }
152             }
153             else if (cur->right != SNode && SNode->right == NULL)    //後繼節點不為待刪除節點的右子樹,並且該後繼節點沒有右子樹
154             {
155                 if (parent->left == cur)
156                 {
157                     parent->left = SNode;
158                     SNode->left = cur->left;
159                     SNode->right = cur->right;
160                     PSNode->left = NULL;
161                 }
162                 else if (parent->right == cur)
163                 {
164                     parent->right = SNode;
165                     SNode->left = cur->left;
166                     SNode->right = cur->right;
167                     PSNode->left = NULL;
168                 }
169             }
170             else if (cur->right != SNode && SNode->right != NULL)    //後繼節點不為待刪除節點的右子樹,並且該後繼節點有右子樹
171             {
172                 if (parent->left == cur)
173                 {
174                     parent->left = SNode;
175                     PSNode->left = SNode->right;    //後繼節點的右子樹作為後繼節點父節點的左子樹
176                     SNode->left = cur->left;
177                     SNode->right = cur->right;
178                 }
179                 else if (parent->right == cur)
180                 {
181                     parent->right = SNode;
182                     PSNode->left = SNode->right;    //後繼節點的右子樹作為後繼節點父節點的左子樹
183                     SNode->left = cur->left;
184                     SNode->right = cur->right;
185                 }
186             }
187         }
188         free(cur);    //釋放待刪除節點
189     }
190 }
191 
192 /* 新增新節點 */
193 BSTree *AddNewNode(BSTree *cur, int NewData)
194 {
195     if (cur == NULL)
196     {
197         if ((cur = (BSTree *)malloc(sizeof(BSTree))) == NULL)    //建立新節點
198         {
199             printf("記憶體不足");
200             exit(0);
201         }
202         cur->data = NewData;
203         cur->left = NULL;
204         cur->right = NULL;
205 
206         return cur;
207     }
208     if (NewData > cur->data)
209     {
210         cur->right = AddNewNode(cur->right, NewData);
211     }
212     else if (NewData < cur->data)
213     {
214         cur->left = AddNewNode(cur->left, NewData);
215     }
216     else if (NewData == cur->data)
217     {
218         printf("不允許插入重複值\n");
219         exit(0);
220     }
221 
222     return cur;
223 }
224 
225 /* 查詢特定值 */
226 void SearchData(int targ, BSTree *nod)
227 {
228     if (nod != NULL)
229     {
230         if (nod->data == targ)
231         {
232             printf("查詢值存在,值為%d\n", nod->data);
233         }
234         else if (nod->data > targ)
235         {
236             SearchData(targ, nod->left);    //遞迴查詢左子樹
237         }
238         else if (nod->data < targ)
239         {
240             SearchData(targ, nod->right);    //遞迴查詢右子樹
241         }
242     }
243     else if (nod == NULL)
244     {
245         printf("查詢值不存在\n");
246     }
247 }
248 
249 /* 建立二叉查詢樹(資料以前序遍歷順序輸入)*/
250 BSTree *Create_BSTreeNode(BSTree *nod)
251 {
252     int num;
253 
254     scanf_s("%d", &num, 1);
255     if (num == 0)    /* 假定輸入的資料都為正數,以0作為NULL的標誌 */
256     {
257         return NULL;
258     }
259     else
260     {
261         if ((nod = (BSTree *)malloc(sizeof(BSTree))) == NULL)
262         {
263             printf("記憶體空間不足");
264             exit(0);
265         }
266         nod->data = num;
267         nod->left = Create_BSTreeNode(nod->left);
268         nod->right = Create_BSTreeNode(nod->right);
269 
270         return nod;
271     }
272 }
273 
274 /* 前序遍歷二叉樹,並列印 */
275 void PreOrder_Traverse(BSTree *nod, int level)
276 {
277     if (nod == NULL)
278     {
279         return ;
280     }
281 
282     printf("data = %d level = %d\n", nod->data, level);
283     PreOrder_Traverse(nod->left, level + 1);
284     PreOrder_Traverse(nod->right, level + 1);
285 }
View Code

 

因為主要是分析二叉查詢樹的查詢、插入、刪除操作,沒有考慮二叉樹為空 或 待刪除節點為根節點的情況

相關文章