二叉查找樹


二叉查找樹

總結:

1、節點的定義中

1 typedef int Type;
2 
3 typedef struct BSTreeNode{
4     Type   key;                    // 關鍵字(鍵值)
5     struct BSTreeNode *left;    // 左孩子
6     struct BSTreeNode *right;    // 右孩子
7     struct BSTreeNode *parent;    // 父結點
8 }Node, *BSTree;

a. 左右孩子用指針定義,類似於int *left,因為結構體本身就是一種自定義類型,struct BSTreeNode看成系統的類型int不過分。

b. 用了typedef重定義類型,給struct BSTreeNode起了兩個別名Node和*BSTree。

c. 其中的*BSTree起始就相當於 typedef int *m中的*m。

 

2、創建節點中

 1 static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right)
 2 {
 3     Node* p;
 4 
 5     if ((p = (Node *)malloc(sizeof(Node))) == NULL)
 6         return NULL;
 7     p->key = key;
 8     p->left = left;
 9     p->right = right;
10     p->parent = parent;
11 
12     return p;
13 }

a. 用了malloc函數動態分配內存,p = (Node *)malloc(sizeof(Node)),很定式的寫法,malloc出來的是指針,所以強轉成指針,動態申請的空間大小,自然是sizeof(Node),Node是我們定義的結構體的別名

b. if ((p = (Node *)malloc(sizeof(Node))) == NULL) 用動態分配的返回值是否為NULL來判斷是否動態創建成功,c++的動態創建用的是new關鍵字。

c. “沒有”在C語言中表示為NULL,在JAVA中表示為null。

d. 在c和c++中,對象是指針的話,用->來找成員,例如p->key = key;,如果對象是非指針的話,用的是.(點號)來找成員。

 

3、在前序遍歷中

1 void preorder_bstree(BSTree tree)
2 {
3     if(tree != NULL)
4     {
5         printf("%d ", tree->key);
6         preorder_bstree(tree->left);
7         preorder_bstree(tree->right);
8     }
9 }

a、非空的節點我們才有必要輸出它的值和找左右孩子才有意義

 

4、在遞歸查找中

1 Node* bstree_search(BSTree x, Type key)
2 {
3     if (x==NULL || x->key==key)
4         return x;
5     if (key < x->key)
6         return bstree_search(x->left, key);
7     else
8         return bstree_search(x->right, key);
9 }

要查的樹的根節點是x,我們要查的鍵值是key,返回的節點是我們找到的那個值為key的節點,沒找到返回NULL

a. 第三行,空樹就沒有必要查了吧,這里也包括反復遞歸的過程中沒有找到的而到NULL節點的情況,直接返回NULL就可以了,

b. 如果找到key的話,直接返回那個節點即可

c. 如果當前查找的key的值小於根的值,就往左子樹跑,否則往右子樹跑。遞歸調用。

 

5、查找最大值的代碼

1 Node* bstree_maximum(BSTree tree)
2 {
3     if (tree == NULL)
4         return NULL;
5     while(tree->right != NULL)
6         tree = tree->right;
7     return tree;
8 }

要查找的是樹的根節點tree,返回的是值最大的那個節點

a. 如果樹為空的話,直接返回即可。

b. 如果右子樹存在的情況下,也就是右子樹不為空的情況就往右子樹找。

c. 找最小值和找最大值幾乎一模一樣,一個往左一個往右。

 

6、查找前驅節點

節點的前驅:是該節點的左子樹中的最大節點。 

 1 Node* bstree_predecessor(Node *x)
 2 {
 3     // 如果x存在左孩子,則"x的前驅結點"為 "以其左孩子為根的子樹的最大結點"。
 4     if (x->left != NULL)
 5         return bstree_maximum(x->left);
 6 
 7     // 如果x沒有左孩子。則x有以下兩種可能:
 8     // (01) x是"一個右孩子",則"x的前驅結點"為 "它的父結點"。
 9     // (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。
10     Node* y = x->parent;
11     while ((y!=NULL) && (x==y->left))
12     {
13         x = y;
14         y = y->parent;
15     }
16 
17     return y;
18 }

5這個節點有左孩子,所以前驅就是子樹中的最大節點4.

7這個節點沒有左孩子且7是右孩子,所以前驅就是父親節點6.

2這個節點沒有左孩子且2這個節點是左孩子,找到第一個父親的祖先有右孩子的1就是它的前驅。同理節點8也是。

a. 第一種情況用到了上面的求樹中最大值的算法

b. 節點y是x的父親,去找父親第一個有右孩子的祖先,就是x的前驅。因為走到節點5和1的時候發現5不是節點1的左孩子,就停止循環,故找到節點1.

c. 第十行的那個代碼包括第二種和第三種情況,比如找7(第二種情況)的前驅時候,把這段代碼代進去算就會發現可以用。因為不是左孩子,沒進循環,直接返回父親了。

 

7、查找后繼節點

節點的后繼:是該節點的右子樹中的最小節點。

 1 Node* bstree_successor(Node *x)
 2 {
 3     // 如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。
 4     if (x->right != NULL)
 5         return bstree_minimum(x->right);
 6 
 7     // 如果x沒有右孩子。則x有以下兩種可能:
 8     // (01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。
 9     // (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。
10     Node* y = x->parent;
11     while ((y!=NULL) && (x==y->right))
12     {
13         x = y;
14         y = y->parent;
15     }
16 
17     return y;
18 }

查找的是節點x的后繼節點,返回的就是后繼節點的那個點

如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。

比如節點1的后繼節點就是2

如果x沒有右孩子。則x有以下兩種可能:
(01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。

比如說節點2,本身沒有右孩子,自己又是左孩子,所以后繼就是父親,因為左子樹總比根小
(02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。

比如說節點5,沒有右孩子,自己又是右孩子,那就找父親里面第一個有左孩子的就好,節點5的后繼就是6.

其實5沒有右孩子,說明5是這顆子樹里面最大的,而這樣的結構只有變成左子樹才能找到比它大的,所以找到的第一個左子樹的分叉6就是后繼。

a. 代碼第三行就是有右孩子直接右孩子那棵樹返回最小的

b. 第十行就是后面兩種情況,去找左子樹來找比它大的做它的后繼,因為根比左子樹大,都是找到第一個比它大的就是。

 

8、插入節點

 1 static Node* bstree_insert(BSTree tree, Node *z)
 2 {
 3     Node *y = NULL;
 4     Node *x = tree;
 5 
 6     // 查找z的插入位置
 7     while (x != NULL)
 8     {
 9         y = x;
10         if (z->key < x->key)
11             x = x->left;
12         else
13             x = x->right;
14     }
15 
16     z->parent = y;
17     if (y==NULL)
18         tree = z;
19     else if (z->key < y->key)
20         y->left = z;
21     else
22         y->right = z;
23 
24     return tree;
25 }
26 
27 Node* insert_bstree(BSTree tree, Type key)
28 {
29     Node *z;    // 新建結點
30 
31     // 如果新建結點失敗,則返回。
32     if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
33         return tree;
34 
35     return bstree_insert(tree, z);
36 }

bstree_insert(tree, z)是內部函數,它的作用是:將結點(z)插入到二叉樹(tree)中,並返回插入節點后的根節點。
insert_bstree(tree, key)是對外接口,它的作用是:在樹中新增節點,key是節點的值;並返回插入節點后的根節點。

注:本文實現的二叉查找樹是允許插入相同鍵值的節點的!

insert_bstree(tree, key)中

a. 32行的if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)是節點沒有創建成功返回NULL

b. 這里新建節點直接Node *z;,相當於int a;這樣做是只聲明了並沒有給它內存空間,所以在create函數里面用了malloc給它內存空間。這里和JAVA一樣。

bstree_insert(tree, z)中

a. 因為是在鏈上操作,所以插入操作需要需要記錄兩個點,把插入的點插到中間就好了,防止斷鏈。

b. 第6到14行代碼,相等插在右子樹上,6-14行就是找到那個為NULL的插入的位置

c. 16-24行插入操作,如果樹為空直接插入z做根節點,找到是插入左邊還是右邊,因為節點是插入在y的子樹上的,這里的y是插入位置的父親

 

9、刪除操作

二叉樹的刪除可以算是二叉樹最為復雜的操作,刪除的時候要考慮到很多種情況:
1.被刪除的節點是葉子節點
2.被刪除的節點只有左孩子節點
3.被刪除的節點只有右孩子節點
4.被刪除的有兩個孩子節點
所以在刪除的時候,這4種情況都必須考慮進去,並且這4中情況之下,還會有細的划分,下面就細說怎么刪除。

bstree_delete(tree, z)是內部函數,它的作用是:刪除二叉樹(tree)中的節點(z),並返回刪除節點后的根節點。
delete_bstree(tree, key)是對外接口,它的作用是:在樹中查找鍵值為key的節點,找到的話就刪除該節點;並返回刪除節點后的根節點。

 1 //tree是樹的根節點,z是我們要刪除的那個節點 
 2 static Node* bstree_delete(BSTree tree, Node *z)
 3 {
 4     Node *x=NULL;
 5     Node *y=NULL;
 6     
 7     //左孩子或者右孩子為空的情況,就是只有一個孩子或者兩個孩子都沒有的情況 
 8     //直接將z賦值給y 
 9     if ((z->left == NULL) || (z->right == NULL) )
10         y = z;
11     else//兩個孩子都存在的情況下,直接找z的后繼 
12         y = bstree_successor(z);
13 
14     //那個后繼節點的左子樹不為空,將左子樹給x 
15     if (y->left != NULL)
16         x = y->left;
17     else//那個后繼節點的左子樹為空的話,那x就是y的右子樹 
18         x = y->right;
19 
20     //x節點不為空的情況,將y的父親賦值給x的父親,標准的刪除操作,這是為了刪除后繼節點y
21     //因為刪除的思路就是用后繼節點來替換刪除的那個節點 
22     if (x != NULL)
23         x->parent = y->parent;
24 
25     //如果y沒有父親了,y是比較靠近樹根的點 
26     if (y->parent == NULL)
27         tree = x;
28     else if (y == y->parent->left)
29         y->parent->left = x;
30     else
31         y->parent->right = x;
32 
33     if (y != z) 
34         z->key = y->key;
35 
36     if (y!=NULL)
37         free(y);
38 
39     return tree;
40 }
41 
42 Node* delete_bstree(BSTree tree, Type key)
43 {
44     Node *z, *node; 
45 
46     if ((z = bstree_search(tree, key)) != NULL)
47         tree = bstree_delete(tree, z);
48 
49     return tree;
50 }

在delete_bstree(tree, key)中

a. 先調用查找函數找這個節點,如果找到了就刪了這個節點,z是這個找到的節點

在bstree_delete(tree, z)中

a. 第9行的y = bstree_successor(z);是找到z的后繼y,

 

10、打印二叉樹

 1 void print_bstree(BSTree tree, Type key, int direction)
 2 {
 3     if(tree != NULL)
 4     {
 5         if(direction==0)    // tree是根節點
 6             printf("%2d is root\n", tree->key);
 7         else                // tree是分支節點
 8             printf("%2d is %2d's %6s child\n", tree->key, key, direction==1?"right" : "left");
 9 
10         print_bstree(tree->left, tree->key, -1);
11         print_bstree(tree->right,tree->key,  1);
12     }
13 }

print_bstree(tree, key, direction)的作用是打印整顆二叉樹(tree)。其中,tree是二叉樹節點,key是二叉樹的鍵值,而direction表示該節點的類型:

direction為 0,表示該節點是根節點;
direction為-1,表示該節點是它的父結點的左孩子;
direction為 1,表示該節點是它的父結點的右孩子。

a. 這樣子用一個 中間變量direction可以指明每一個點是左還是右還是根。

 

11、 銷毀二叉樹

 1 void destroy_bstree(BSTree tree)
 2 {
 3     if (tree==NULL)
 4         return ;
 5 
 6     if (tree->left != NULL)
 7         destroy_bstree(tree->left);
 8     if (tree->right != NULL)
 9         destroy_bstree(tree->right);
10 
11     free(tree);
12 }

a. 如果tree為空,不用銷毀,在銷毀了左右之后再來銷毀根

b. 銷毀的核心操作就是free,這個操作是釋放指針所指的內存空間,按JAVA里面的說法就是釋放堆空間,因為棧空間系統會自動維護的,雖然JAVA里面沒有指針,但是引用本質也是指針。

 

 

概要

       本章先對二叉樹的相關理論知識進行介紹,然后給出C語言的詳細實現。關於二叉樹的學習,需要說明的是:它並不難,不僅不難,而且它非常簡單。初次接觸樹的時候,我也覺得它似乎很難;而之所產生這種感覺主要是由於二叉樹有一大堆陌生的概念、性質等內容。而當我真正的實現了二叉樹再回過頭來看它的相關概念和性質的時候,覺得原來它是如此的簡單!因此,建議在學習二叉樹的時候:先對二叉樹基本的概念、性質有個基本了解,遇到難懂的知識點,可以畫圖來幫助理解;在有個基本的概念之后,再親自動手實現二叉查找樹(這一點至關重要!);最后再回過頭來總結一下二叉樹的理論知識時,你會發現——它的確很簡單!在代碼實踐中,我以"二叉查找樹,而不是單純的二叉樹"為例子進行說明,單純的二叉樹非常簡單,實際使用很少。況且掌握了二叉查找樹,二叉樹也就自然掌握了。

      本篇實現的二叉查找樹是C語言版的,后面章節再分別給出C++和Java版本的實現。您可以根據自己熟悉的語言進行實踐學習!

      請務必深刻理解、實踐並掌握"二叉查找樹"!它是后面學習AVL樹、伸展樹、紅黑樹等相關樹結構的基石!

 

目錄
1. 樹的介紹
2. 二叉樹的介紹
3. 二叉查找樹的C實現
4. 二叉查找樹的C測試程序

轉載請注明出處:http://www.cnblogs.com/skywang12345/p/3576328.html


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

(01). 二叉查找樹(一)之 圖文解析 和 C語言的實現
(02). 二叉查找樹(二)之 C++的實現
(03). 二叉查找樹(三)之 Java的實現

 

樹的介紹

1. 樹的定義

樹是一種數據結構,它是由n(n>=1)個有限節點組成一個具有層次關系的集合。

把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:
(01) 每個節點有零個或多個子節點;
(02) 沒有父節點的節點稱為根節點;
(03) 每一個非根節點有且只有一個父節點;
(04) 除了根節點外,每個子節點可以分為多個不相交的子樹。

 

2. 樹的基本術語

若一個結點有子樹,那么該結點稱為子樹根的"雙親",子樹的根是該結點的"孩子"。有相同雙親的結點互為"兄弟"。一個結點的所有子樹上的任何結點都是該結點的后裔。從根結點到某個結點的路徑上的所有結點都是該結點的祖先。

結點的度:結點擁有的子樹的數目。
葉子:度為零的結點。
分支結點:度不為零的結點。
樹的度:樹中結點的最大的度。

層次:根結點的層次為1,其余結點的層次等於該結點的雙親結點的層次加1。
樹的高度:樹中結點的最大層次。
無序樹:如果樹中結點的各子樹之間的次序是不重要的,可以交換位置。
有序樹:如果樹中結點的各子樹之間的次序是重要的, 不可以交換位置。
森林:0個或多個不相交的樹組成。對森林加上一個根,森林即成為樹;刪去根,樹即成為森林。

 

二叉樹的介紹

1. 二叉樹的定義

二叉樹是每個節點最多有兩個子樹的樹結構。它有五種基本形態:二叉樹可以是空集;根可以有空的左子樹或右子樹;或者左、右子樹皆為空。

 

2. 二叉樹的性質

二叉樹有以下幾個性質:TODO(上標和下標)
性質1:二叉樹第i層上的結點數目最多為 2{i-1} (i≥1)。
性質2:深度為k的二叉樹至多有2{k}-1個結點(k≥1)。
性質3:包含n個結點的二叉樹的高度至少為log2 (n+1)
性質4:在任意一棵二叉樹中,若終端結點的個數為n0,度為2的結點數為n2,則n0=n2+1

 

2.1 性質1:二叉樹第i層上的結點數目最多為 2{i-1} (i≥1)

證明:下面用"數學歸納法"進行證明。
        (01) 當i=1時,第i層的節點數目為2{i-1}=2{0}=1。因為第1層上只有一個根結點,所以命題成立。
        (02) 假設當i>1,第i層的節點數目為2{i-1}。這個是根據(01)推斷出來的!
               下面根據這個假設,推斷出"第(i+1)層的節點數目為2{i}"即可。
                由於二叉樹的每個結點至多有兩個孩子,故"第(i+1)層上的結點數目" 最多是 "第i層的結點數目的2倍"。即,第(i+1)層上的結點數目最大值=2×2{i-1}=2{i}
                故假設成立,原命題得證!

 

2.2 性質2:深度為k的二叉樹至多有2{k}-1個結點(k≥1)

證明:在具有相同深度的二叉樹中,當每一層都含有最大結點數時,其樹中結點數最多。利用"性質1"可知,深度為k的二叉樹的結點數至多為:
           20+21+…+2k-1=2k-1
           故原命題得證!

 

2.3 性質3:包含n個結點的二叉樹的高度至少為log2 (n+1)

證明:根據"性質2"可知,高度為h的二叉樹最多有2{h}–1個結點。反之,對於包含n個節點的二叉樹的高度至少為log2(n+1)。

 

2.4 性質4:在任意一棵二叉樹中,若終端結點的個數為n0,度為2的結點數為n2,則n0=n2+1

證明:因為二叉樹中所有結點的度數均不大於2,所以結點總數(記為n)="0度結點數(n0)" + "1度結點數(n1)" + "2度結點數(n2)"。由此,得到等式一。
         (等式一) n=n0+n1+n2
      另一方面,0度結點沒有孩子,1度結點有一個孩子,2度結點有兩個孩子,故二叉樹中孩子結點總數是:n1+2n2。此外,只有根不是任何結點的孩子。故二叉樹中的結點總數又可表示為等式二。
         (等式二) n=n1+2n2+1
        由(等式一)和(等式二)計算得到:n0=n2+1。原命題得證!

 

3. 滿二叉樹,完全二叉樹和二叉查找樹

3.1 滿二叉樹

定義:高度為h,並且由2{h} –1個結點的二叉樹,被稱為滿二叉樹。

 

3.2 完全二叉樹

定義:一棵二叉樹中,只有最下面兩層結點的度可以小於2,並且最下一層的葉結點集中在靠左的若干位置上。這樣的二叉樹稱為完全二叉樹。
特點:葉子結點只能出現在最下層和次下層,且最下層的葉子結點集中在樹的左部。顯然,一棵滿二叉樹必定是一棵完全二叉樹,而完全二叉樹未必是滿二叉樹。

 

3.3 二叉查找樹

定義:二叉查找樹(Binary Search Tree),又被稱為二叉搜索樹。設x為二叉查找樹中的一個結點,x節點包含關鍵字key,節點x的key值記為key[x]。如果y是x的左子樹中的一個結點,則key[y] <= key[x];如果y是x的右子樹的一個結點,則key[y] >= key[x]。

在二叉查找樹中:
(01) 若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
(02) 任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
(03) 任意節點的左、右子樹也分別為二叉查找樹。
(04) 沒有鍵值相等的節點(no duplicate nodes)。

在實際應用中,二叉查找樹的使用比較多。下面,用C語言實現二叉查找樹。

 

二叉查找樹的C實現

1. 節點定義

1.1 節點定義

復制代碼
typedef int Type;

typedef struct BSTreeNode{
    Type   key;                    // 關鍵字(鍵值)
    struct BSTreeNode *left;    // 左孩子
    struct BSTreeNode *right;    // 右孩子
    struct BSTreeNode *parent;    // 父結點
}Node, *BSTree;
復制代碼

二叉查找樹的節點包含的基本信息:
(01) key       -- 它是關鍵字,是用來對二叉查找樹的節點進行排序的。
(02) left       -- 它指向當前節點的左孩子。
(03) right    -- 它指向當前節點的右孩子。
(04) parent -- 它指向當前節點的父結點。

 

1.2 創建節點

創建節點的代碼

復制代碼
static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right)
{
    Node* p;

    if ((p = (Node *)malloc(sizeof(Node))) == NULL)
        return NULL;
    p->key = key;
    p->left = left;
    p->right = right;
    p->parent = parent;

    return p;
}
復制代碼

 

2 遍歷

這里講解前序遍歷中序遍歷后序遍歷3種方式。

2.1 前序遍歷

若二叉樹非空,則執行以下操作:
(01) 訪問根結點;
(02) 先序遍歷左子樹;
(03) 先序遍歷右子樹。

前序遍歷代碼

復制代碼
void preorder_bstree(BSTree tree)
{
    if(tree != NULL)
    {
        printf("%d ", tree->key);
        preorder_bstree(tree->left);
        preorder_bstree(tree->right);
    }
}
復制代碼

 

2.2 中序遍歷

若二叉樹非空,則執行以下操作:
(01) 中序遍歷左子樹;
(02) 訪問根結點;
(03) 中序遍歷右子樹。

中序遍歷代碼

復制代碼
void inorder_bstree(BSTree tree)
{
    if(tree != NULL)
    {
        inorder_bstree(tree->left);
        printf("%d ", tree->key);
        inorder_bstree(tree->right);
    }
}
復制代碼

 

2.3 后序遍歷

若二叉樹非空,則執行以下操作:
(01) 后序遍歷左子樹;
(02) 后序遍歷右子樹;
(03) 訪問根結點。

后序遍歷代碼

復制代碼
void postorder_bstree(BSTree tree)
{
    if(tree != NULL)
    {
        postorder_bstree(tree->left);
        postorder_bstree(tree->right);
        printf("%d ", tree->key);
    }
}
復制代碼

 

 

下面通過例子對這些遍歷方式進行介紹。

對於上面的二叉樹而言,
(01) 前序遍歷結果: 3 1 2 5 4 6
(02) 中序遍歷結果: 1 2 3 4 5 6 
(03) 后序遍歷結果: 2 1 4 6 5 3

 

3. 查找

遞歸版本的代碼

復制代碼
Node* bstree_search(BSTree x, Type key)
{
    if (x==NULL || x->key==key)
        return x;

    if (key < x->key)
        return bstree_search(x->left, key);
    else
        return bstree_search(x->right, key);
}
復制代碼

非遞歸版本的代碼

復制代碼
Node* iterative_bstree_search(BSTree x, Type key)
{
    while ((x!=NULL) && (x->key!=key))
    {
        if (key < x->key)
            x = x->left;
        else
            x = x->right;
    }

    return x;
}
復制代碼

 

4. 最大值和最小值

查找最大值的代碼

復制代碼
Node* bstree_maximum(BSTree tree)
{
    if (tree == NULL)
        return NULL;

    while(tree->right != NULL)
        tree = tree->right;
    return tree;
}
復制代碼

查找最小值的代碼

復制代碼
Node* bstree_minimum(BSTree tree)
{
    if (tree == NULL)
        return NULL;

    while(tree->left != NULL)
        tree = tree->left;
    return tree;
}
復制代碼


5. 前驅和后繼

節點的前驅:是該節點的左子樹中的最大節點。
節點的后繼:是該節點的右子樹中的最小節點。

查找前驅節點的代碼

復制代碼
Node* bstree_predecessor(Node *x)
{
    // 如果x存在左孩子,則"x的前驅結點"為 "以其左孩子為根的子樹的最大結點"。
    if (x->left != NULL)
        return bstree_maximum(x->left);

    // 如果x沒有左孩子。則x有以下兩種可能:
    // (01) x是"一個右孩子",則"x的前驅結點"為 "它的父結點"。
    // (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。
    Node* y = x->parent;
    while ((y!=NULL) && (x==y->left))
    {
        x = y;
        y = y->parent;
    }

    return y;
}
復制代碼

查找后繼節點的代碼

復制代碼
Node* bstree_successor(Node *x)
{
    // 如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。
    if (x->right != NULL)
        return bstree_minimum(x->right);

    // 如果x沒有右孩子。則x有以下兩種可能:
    // (01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。
    // (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。
    Node* y = x->parent;
    while ((y!=NULL) && (x==y->right))
    {
        x = y;
        y = y->parent;
    }

    return y;
}
復制代碼

 

6. 插入

插入節點的代碼

復制代碼
static Node* bstree_insert(BSTree tree, Node *z)
{
    Node *y = NULL;
    Node *x = tree;

    // 查找z的插入位置
    while (x != NULL)
    {
        y = x;
        if (z->key < x->key)
            x = x->left;
        else
            x = x->right;
    }

    z->parent = y;
    if (y==NULL)
        tree = z;
    else if (z->key < y->key)
        y->left = z;
    else
        y->right = z;

    return tree;
}

Node* insert_bstree(BSTree tree, Type key)
{
    Node *z;    // 新建結點

    // 如果新建結點失敗,則返回。
    if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
        return tree;

    return bstree_insert(tree, z);
}
復制代碼

bstree_insert(tree, z)是內部函數,它的作用是:將結點(z)插入到二叉樹(tree)中,並返回插入節點后的根節點。
insert_bstree(tree, key)是對外接口,它的作用是:在樹中新增節點,key是節點的值;並返回插入節點后的根節點。

 

注:本文實現的二叉查找樹是允許插入相同鍵值的節點的!若用戶不希望插入相同鍵值的節點,將bstree_insert()修改為以下代碼即可。

復制代碼
static Node* bstree_insert(BSTree tree, Node *z)
{
    Node *y = NULL;
    Node *x = tree;

    // 查找z的插入位置
    while (x != NULL)
    {
        y = x;
        if (z->key < x->key)
            x = x->left;
        else  if (z->key > x->key)
            x = x->right;
        else
        {
            free(z); // 釋放之前分配的系統。
            return tree;
        }
    }

    z->parent = y;
    if (y==NULL)
        tree = z;
    else if (z->key < y->key)
        y->left = z;
    else
        y->right = z;

    return tree;
}
復制代碼

 

7. 刪除

刪除節點的代碼

復制代碼
static Node* bstree_delete(BSTree tree, Node *z)
{
    Node *x=NULL;
    Node *y=NULL;

    if ((z->left == NULL) || (z->right == NULL) )
        y = z;
    else
        y = bstree_successor(z);

    if (y->left != NULL)
        x = y->left;
    else
        x = y->right;

    if (x != NULL)
        x->parent = y->parent;

    if (y->parent == NULL)
        tree = x;
    else if (y == y->parent->left)
        y->parent->left = x;
    else
        y->parent->right = x;

    if (y != z) 
        z->key = y->key;

    if (y!=NULL)
        free(y);

    return tree;
}

Node* delete_bstree(BSTree tree, Type key)
{
    Node *z, *node; 

    if ((z = bstree_search(tree, key)) != NULL)
        tree = bstree_delete(tree, z);

    return tree;
}
復制代碼

bstree_delete(tree, z)是內部函數,它的作用是:刪除二叉樹(tree)中的節點(z),並返回刪除節點后的根節點。
delete_bstree(tree, key)是對外接口,它的作用是:在樹中查找鍵值為key的節點,找到的話就刪除該節點;並返回刪除節點后的根節點。


8. 打印

打印二叉樹的代碼

復制代碼
void print_bstree(BSTree tree, Type key, int direction)
{
    if(tree != NULL)
    {
        if(direction==0)    // tree是根節點
            printf("%2d is root\n", tree->key);
        else                // tree是分支節點
            printf("%2d is %2d's %6s child\n", tree->key, key, direction==1?"right" : "left");

        print_bstree(tree->left, tree->key, -1);
        print_bstree(tree->right,tree->key,  1);
    }
}
復制代碼

print_bstree(tree, key, direction)的作用是打印整顆二叉樹(tree)。其中,tree是二叉樹節點,key是二叉樹的鍵值,而direction表示該節點的類型:

direction為 0,表示該節點是根節點;
direction為-1,表示該節點是它的父結點的左孩子;
direction為 1,表示該節點是它的父結點的右孩子。

 

9. 銷毀二叉樹

銷毀二叉樹的代碼

復制代碼
void destroy_bstree(BSTree tree)
{
    if (tree==NULL)
        return ;

    if (tree->left != NULL)
        destroy_bstree(tree->left);
    if (tree->right != NULL)
        destroy_bstree(tree->right);

    free(tree);
}
復制代碼

 

完整的實現代碼

二叉查找樹的頭文件(bstree.h)

復制代碼
 1 #ifndef _BINARY_SEARCH_TREE_H_
 2 #define _BINARY_SEARCH_TREE_H_
 3 
 4 typedef int Type;
 5 
 6 typedef struct BSTreeNode{
 7     Type   key;                    // 關鍵字(鍵值)
 8     struct BSTreeNode *left;    // 左孩子
 9     struct BSTreeNode *right;    // 右孩子
10     struct BSTreeNode *parent;    // 父結點
11 }Node, *BSTree;
12 
13 // 前序遍歷"二叉樹"
14 void preorder_bstree(BSTree tree);
15 // 中序遍歷"二叉樹"
16 void inorder_bstree(BSTree tree);
17 // 后序遍歷"二叉樹"
18 void postorder_bstree(BSTree tree);
19 
20 // (遞歸實現)查找"二叉樹x"中鍵值為key的節點
21 Node* bstree_search(BSTree x, Type key);
22 // (非遞歸實現)查找"二叉樹x"中鍵值為key的節點
23 Node* iterative_bstree_search(BSTree x, Type key);
24 
25 // 查找最小結點:返回tree為根結點的二叉樹的最小結點。
26 Node* bstree_minimum(BSTree tree);
27 // 查找最大結點:返回tree為根結點的二叉樹的最大結點。
28 Node* bstree_maximum(BSTree tree);
29 
30 // 找結點(x)的后繼結點。即,查找"二叉樹中數據值大於該結點"的"最小結點"。
31 Node* bstree_successor(Node *x);
32 // 找結點(x)的前驅結點。即,查找"二叉樹中數據值小於該結點"的"最大結點"。
33 Node* bstree_predecessor(Node *x);
34 
35 // 將結點插入到二叉樹中,並返回根節點
36 Node* insert_bstree(BSTree tree, Type key);
37 
38 // 刪除結點(key為節點的值),並返回根節點
39 Node* delete_bstree(BSTree tree, Type key);
40 
41 // 銷毀二叉樹
42 void destroy_bstree(BSTree tree);
43 
44 // 打印二叉樹
45 void print_bstree(BSTree tree, Type key, int direction);
46 
47 #endif
復制代碼

二叉查找樹的實現文件(bstree.c)

復制代碼
  1 /**
  2  * 二叉搜索樹(C語言): C語言實現的二叉搜索樹。
  3  *
  4  * @author skywang
  5  * @date 2013/11/07
  6  */
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include "bstree.h"
 11 
 12 
 13 /*
 14  * 前序遍歷"二叉樹"
 15  */
 16 void preorder_bstree(BSTree tree)
 17 {
 18     if(tree != NULL)
 19     {
 20         printf("%d ", tree->key);
 21         preorder_bstree(tree->left);
 22         preorder_bstree(tree->right);
 23     }
 24 }
 25 
 26 /*
 27  * 中序遍歷"二叉樹"
 28  */
 29 void inorder_bstree(BSTree tree)
 30 {
 31     if(tree != NULL)
 32     {
 33         inorder_bstree(tree->left);
 34         printf("%d ", tree->key);
 35         inorder_bstree(tree->right);
 36     }
 37 }
 38 
 39 /*
 40  * 后序遍歷"二叉樹"
 41  */
 42 void postorder_bstree(BSTree tree)
 43 {
 44     if(tree != NULL)
 45     {
 46         postorder_bstree(tree->left);
 47         postorder_bstree(tree->right);
 48         printf("%d ", tree->key);
 49     }
 50 }
 51 
 52 /*
 53  * (遞歸實現)查找"二叉樹x"中鍵值為key的節點
 54  */
 55 Node* bstree_search(BSTree x, Type key)
 56 {
 57     if (x==NULL || x->key==key)
 58         return x;
 59 
 60     if (key < x->key)
 61         return bstree_search(x->left, key);
 62     else
 63         return bstree_search(x->right, key);
 64 }
 65 
 66 /*
 67  * (非遞歸實現)查找"二叉樹x"中鍵值為key的節點
 68  */
 69 Node* iterative_bstree_search(BSTree x, Type key)
 70 {
 71     while ((x!=NULL) && (x->key!=key))
 72     {
 73         if (key < x->key)
 74             x = x->left;
 75         else
 76             x = x->right;
 77     }
 78 
 79     return x;
 80 }
 81 
 82 /* 
 83  * 查找最小結點:返回tree為根結點的二叉樹的最小結點。
 84  */
 85 Node* bstree_minimum(BSTree tree)
 86 {
 87     if (tree == NULL)
 88         return NULL;
 89 
 90     while(tree->left != NULL)
 91         tree = tree->left;
 92     return tree;
 93 }
 94  
 95 /* 
 96  * 查找最大結點:返回tree為根結點的二叉樹的最大結點。
 97  */
 98 Node* bstree_maximum(BSTree tree)
 99 {
100     if (tree == NULL)
101         return NULL;
102 
103     while(tree->right != NULL)
104         tree = tree->right;
105     return tree;
106 }
107 
108 /* 
109  * 找結點(x)的后繼結點。即,查找"二叉樹中數據值大於該結點"的"最小結點"。
110  */
111 Node* bstree_successor(Node *x)
112 {
113     // 如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。
114     if (x->right != NULL)
115         return bstree_minimum(x->right);
116 
117     // 如果x沒有右孩子。則x有以下兩種可能:
118     // (01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。
119     // (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。
120     Node* y = x->parent;
121     while ((y!=NULL) && (x==y->right))
122     {
123         x = y;
124         y = y->parent;
125     }
126 
127     return y;
128 }
129  
130 /* 
131  * 找結點(x)的前驅結點。即,查找"二叉樹中數據值小於該結點"的"最大結點"。
132  */
133 Node* bstree_predecessor(Node *x)
134 {
135     // 如果x存在左孩子,則"x的前驅結點"為 "以其左孩子為根的子樹的最大結點"。
136     if (x->left != NULL)
137         return bstree_maximum(x->left);
138 
139     // 如果x沒有左孩子。則x有以下兩種可能:
140     // (01) x是"一個右孩子",則"x的前驅結點"為 "它的父結點"。
141     // (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。
142     Node* y = x->parent;
143     while ((y!=NULL) && (x==y->left))
144     {
145         x = y;
146         y = y->parent;
147     }
148 
149     return y;
150 }
151 
152 /*
153  * 創建並返回二叉樹結點。
154  *
155  * 參數說明:
156  *     key 是鍵值。
157  *     parent 是父結點。
158  *     left 是左孩子。
159  *     right 是右孩子。
160  */
161 static Node* create_bstree_node(Type key, Node *parent, Node *left, Node* right)
162 {
163     Node* p;
164 
165     if ((p = (Node *)malloc(sizeof(Node))) == NULL)
166         return NULL;
167     p->key = key;
168     p->left = left;
169     p->right = right;
170     p->parent = parent;
171 
172     return p;
173 }
174 
175 /* 
176  * 將結點插入到二叉樹中
177  *
178  * 參數說明:
179  *     tree 二叉樹的根結點
180  *     z 插入的結點
181  * 返回值:
182  *     根節點
183  */
184 static Node* bstree_insert(BSTree tree, Node *z)
185 {
186     Node *y = NULL;
187     Node *x = tree;
188 
189     // 查找z的插入位置
190     while (x != NULL)
191     {
192         y = x;
193         if (z->key < x->key)
194             x = x->left;
195         else
196             x = x->right;
197     }
198 
199     z->parent = y;
200     if (y==NULL)
201         tree = z;
202     else if (z->key < y->key)
203         y->left = z;
204     else
205         y->right = z;
206 
207     return tree;
208 }
209 
210 /* 
211  * 新建結點(key),並將其插入到二叉樹中
212  *
213  * 參數說明:
214  *     tree 二叉樹的根結點
215  *     key 插入結點的鍵值
216  * 返回值:
217  *     根節點
218  */
219 Node* insert_bstree(BSTree tree, Type key)
220 {
221     Node *z;    // 新建結點
222 
223     // 如果新建結點失敗,則返回。
224     if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
225         return tree;
226 
227     return bstree_insert(tree, z);
228 }
229 
230 /* 
231  * 刪除結點(z),並返回根節點
232  *
233  * 參數說明:
234  *     tree 二叉樹的根結點
235  *     z 刪除的結點
236  * 返回值:
237  *     根節點
238  */
239 static Node* bstree_delete(BSTree tree, Node *z)
240 {
241     Node *x=NULL;
242     Node *y=NULL;
243 
244     if ((z->left == NULL) || (z->right == NULL) )
245         y = z;
246     else
247         y = bstree_successor(z);
248 
249     if (y->left != NULL)
250         x = y->left;
251     else
252         x = y->right;
253 
254     if (x != NULL)
255         x->parent = y->parent;
256 
257     if (y->parent == NULL)
258         tree = x;
259     else if (y == y->parent->left)
260         y->parent->left = x;
261     else
262         y->parent->right = x;
263 
264     if (y != z) 
265         z->key = y->key;
266 
267     if (y!=NULL)
268         free(y);
269 
270     return tree;
271 }
272 
273 /* 
274  * 刪除結點(key為節點的鍵值),並返回根節點
275  *
276  * 參數說明:
277  *     tree 二叉樹的根結點
278  *     z 刪除的結點
279  * 返回值:
280  *     根節點
281  */
282 Node* delete_bstree(BSTree tree, Type key)
283 {
284     Node *z, *node; 
285 
286     if ((z = bstree_search(tree, key)) != NULL)
287         tree = bstree_delete(tree, z);
288 
289     return tree;
290 }
291 
292 /*
293  * 銷毀二叉樹
294  */
295 void destroy_bstree(BSTree tree)
296 {
297     if (tree==NULL)
298         return ;
299 
300     if (tree->left != NULL)
301         destroy_bstree(tree->left);
302     if (tree->right != NULL)
303         destroy_bstree(tree->right);
304 
305     free(tree);
306 }
307 
308 /*
309  * 打印"二叉樹"
310  *
311  * tree       -- 二叉樹的節點
312  * key        -- 節點的鍵值 
313  * direction  --  0,表示該節點是根節點;
314  *               -1,表示該節點是它的父結點的左孩子;
315  *                1,表示該節點是它的父結點的右孩子。
316  */
317 void print_bstree(BSTree tree, Type key, int direction)
318 {
319     if(tree != NULL)
320     {
321         if(direction==0)    // tree是根節點
322             printf("%2d is root\n", tree->key);
323         else                // tree是分支節點
324             printf("%2d is %2d's %6s child\n", tree->key, key, direction==1?"right" : "left");
325 
326         print_bstree(tree->left, tree->key, -1);
327         print_bstree(tree->right,tree->key,  1);
328     }
329 }
復制代碼

二叉查找樹的測試程序(btree_test.c)

復制代碼
 1 /**
 2  * C 語言: 二叉查找樹
 3  *
 4  * @author skywang
 5  * @date 2013/11/07
 6  */
 7 
 8 #include <stdio.h>
 9 #include "bstree.h"
10 
11 static int arr[]= {1,5,4,3,2,6};
12 #define TBL_SIZE(a) ( (sizeof(a)) / (sizeof(a[0])) )
13 
14 void main()
15 {
16     int i, ilen;
17     BSTree root=NULL;
18 
19     printf("== 依次添加: ");
20     ilen = TBL_SIZE(arr);
21     for(i=0; i<ilen; i++)
22     {
23         printf("%d ", arr[i]);
24         root = insert_bstree(root, arr[i]);
25     }
26 
27     printf("\n== 前序遍歷: ");
28     preorder_bstree(root);
29 
30     printf("\n== 中序遍歷: ");
31     inorder_bstree(root);
32 
33     printf("\n== 后序遍歷: ");
34     postorder_bstree(root);
35     printf("\n");
36 
37     printf("== 最小值: %d\n", bstree_minimum(root)->key);
38     printf("== 最大值: %d\n", bstree_maximum(root)->key);
39     printf("== 樹的詳細信息: \n");
40     print_bstree(root, root->key, 0);
41 
42     printf("\n== 刪除根節點: %d", arr[3]);
43     root = delete_bstree(root, arr[3]);
44 
45     printf("\n== 中序遍歷: ");
46     inorder_bstree(root);
47     printf("\n");
48 
49     // 銷毀二叉樹
50     destroy_bstree(root);
51 }
復制代碼

 

二叉查找樹的C測試程序

上面的btree_test.c是二叉查找樹的測試程序,它的運行結果如下:

復制代碼
== 依次添加: 1 5 4 3 2 6 
== 前序遍歷: 1 5 4 3 2 6 
== 中序遍歷: 1 2 3 4 5 6 
== 后序遍歷: 2 3 4 6 5 1 
== 最小值: 1
== 最大值: 6
== 樹的詳細信息: 
 1 is root
 5 is  1's  right child
 4 is  5's   left child
 3 is  4's   left child
 2 is  3's   left child
 6 is  5's  right child

== 刪除根節點: 3
== 中序遍歷: 1 2 4 5 6 
復制代碼

 

下面對測試程序的流程進行分析!

(01) 新建"二叉查找樹"root。

 

(02) 向二叉查找樹中依次插入1,5,4,3,2,6 。如下圖所示:

 

(03) 打印樹的信息
插入1,5,4,3,2,6之后,得到的二叉查找樹如下:

前序遍歷結果: 1 5 4 3 2 6 
中序遍歷結果: 1 2 3 4 5 6 
后序遍歷結果: 2 3 4 6 5 1 
最小值是1,而最大值是6。

 

 

(04) 刪除節點3。如下圖所示:

 

(05) 重新遍歷該二叉查找樹。
中序遍歷結果: 1 2 4 5 6

 


免責聲明!

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



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