1. 程式人生 > >寫給自己看的二叉查找樹(1):基本操作

寫給自己看的二叉查找樹(1):基本操作

!= art post 情況下 傳遞函數 無需 最大值 malle nod

搬運自我的CSDN https://blog.csdn.net/u013213111/article/details/88670399

1.定義
二叉樹的每個節點有一個數據成員和兩個指針,兩個指針分別指向左、右子樹。

1 typedef int eletype;
2 
3 typedef struct node
4 {
5     eletype data;
6     struct node *lchild;
7     struct node *rchild;
8 }Tnode;

2.創建一個只含有根節點的空二叉樹
註意要將根節點的左、右子樹初始化為NULL。

Tnode *CreateTree(eletype data)
{
    Tnode 
*root; root = malloc(sizeof(Tnode)); if (!root) printf("Create tree failed."); root->data = data; root->lchild = NULL; root->rchild = NULL; }

3.插入一個節點
通過遞歸來查找應該在何處插入新的節點,若新節點的data小於根節點的data,則在根節點的左子樹部分插入;若新節點的data大於根節點的data,則在根節點的右子樹部分插入。
第一種實現方式:Insert函數的返回值為Tnode *(Tnode型指針)。註意在遞歸時的語句是root->lchild = Insert(root->lchild, data);。

 1 Tnode *Insert(Tnode *root, eletype data)
 2 {
 3     if(root == NULL) {
 4         root = malloc(sizeof(Tnode));
 5         if (!root)
 6             printf("Create tree failed.");
 7         root->data = data;
 8         root->lchild = NULL;
 9         root->rchild = NULL;    
10 } 11 else { 12 if (root->data > data) 13 root->lchild = Insert(root->lchild, data); 14 else 15 root->rchild = Insert(root->rchild, data); 16 } 17 18 return root; 19 }

Insert函數的返回值能不能為void呢?這就是第二種實現方式,用到二級指針來傳遞函數參數。遞歸處的語句是Insert(&((*root)->lchild), data);。
為什麽要用二級指針呢?因為,新節點要插入的地方是一個原樹葉節點的左子樹或者右子樹,而這個樹葉節點的左、右子樹又是NULL,直到插入新節點時才會調用malloc分配空間,如果傳入的是一級指針,即使在最後一層的insert函數中完成了malloc和節點成員的初始化,但返回上一層函數後,並沒有把malloc分配的空間和原樹葉節點的左子樹或右子樹關聯起來,也就是說原樹葉節點的左子樹或者右子樹仍然是NULL。這就好比當希望調用一個函數修改某個變量的時候,不應該將該變量傳值進函數,而是應該傳引用,傳值的話實際上被調用的函數會創建一個該變量的副本,修改的也是這個副本,而不是真正的變量。
關於二級指針的作用,這篇文章二級指針的作用詳解裏寫得比較詳細,總而言之就是:

在函數外部定義一個指針p,在函數內給指針賦值,函數結束後對指針p生效,那麽我們就需要二級指針。

 1 void Insert(Tnode **root, eletype data)
 2 {
 3     if((*root) == NULL) {
 4         *root = malloc(sizeof(Tnode));
 5         if (!root)
 6             printf("Create tree failed.");
 7         (*root)->data = data;
 8         (*root)->lchild = NULL;
 9         (*root)->rchild = NULL;    
10     }    
11     else {
12         if (data < (*root)->data) 
13             Insert(&((*root)->lchild), data);
14         else
15             Insert(&((*root)->rchild), data);
16     }
17 }

4.打印
其實就是遍歷,那麽就有先序遍歷、中序遍歷和後序遍歷三種方法。
註意要檢測一下根節點是不是NULL。
先序遍歷,根-左-右:

1 void PreOrderPrint(Tnode *root) 
2 {
3     if (root == NULL)
4         return;
5     printf("%d ", root->data);
6     PreOrderPrint(root->lchild);
7     PreOrderPrint(root->rchild);
8 }

中序遍歷,左-根-右:

1 void InOrderPrint(Tnode *root) 
2 {
3     if (root == NULL)
4         return;
5     InOrderPrint(root->lchild);
6     printf("%d ", root->data);
7     InOrderPrint(root->rchild);
8 }

後序遍歷,左-右-根:

1 void PostOrderPrint(Tnode *root) 
2 {
3     if (root == NULL)
4         return;
5     PostOrderPrint(root->lchild);
6     PostOrderPrint(root->rchild);
7     printf("%d ", root->data);
8 }

5.根據data查找節點
用遞歸的方式實現,註意一定要記得寫data == root->data的情況。

 1 Tnode *Find(Tnode *root, eletype data)
 2 {
 3     if (root == NULL)
 4         return NULL;
 5     if (data < root->data)
 6         return Find(root->lchild, data);
 7     if (data > root->data)
 8         return Find(root->rchild, data);
 9     if (data == root->data)
10         return root;
11 }

6.查找最小值或最大值
用遞歸或者非遞歸均可。
這裏用遞歸實現查找最小值。

 1 Tnode *FindMin(Tnode *root)
 2 {
 3     if (root == NULL)
 4         return NULL;
 5     else {
 6         if (root->lchild == NULL)
 7             return root;
 8         else
 9             return FindMin(root->lchild);
10     }
11 }

用非遞歸查找最大值,更省略的寫法其實是像FindMin中一樣一直使用root,而無需再定義一個tmp。

1 Tnode *FindMax(Tnode *root)
2 {
3     if (root == NULL)
4         return NULL;
5     Tnode *tmp = root;
6     while (tmp->rchild != NULL)
7         tmp = tmp->rchild;
8     return tmp;
9 }

7.根據data刪除節點
用遞歸的方式實現,函數返回值為Tnode *(Tnode型指針),可以想象假如返回值為void的話,要用和Insert函數第二種實現方式同樣的二級指針來實現。
首先要找到所要刪除的節點的位置。
找到位置所在後,將所要刪除的節點分為兩種情況進行處理,一是有兩個子樹,二是只有一個子樹或者沒有子樹,采用不同的處理方法。
對於只有一個子樹的節點,將子樹上移即可。這其實也包含了沒有子樹的情況,因為沒有子樹的情況下就是將NULL上移。
對於有兩個子樹的節點,將該節點用其右子樹中最小的節點替代,然後刪除右子樹中最小的節點。
最後要記得free掉真正被刪除的節點,註意在有兩個子樹的情況下,真正被刪除的是其右子樹中最小的節點,不斷遞歸“刪除”下去,因此free這個語句只要放在“只有一個子樹或者沒有子樹”這種情況下即可。

 1 Tnode *DeleteNode(Tnode *root, eletype data)
 2 {
 3     if (root == NULL)
 4         return NULL;
 5     if (data < root->data)
 6         root->lchild = DeleteNode(root->lchild, data);
 7     if (data > root->data)
 8         root->rchild = DeleteNode(root->rchild, data);
 9     if (data == root->data) { //Find element to be deleted
10         Tnode *tmp;
11         if (root->lchild && root->rchild) { //has two children
12             tmp = FindMin(root->rchild); //Replace with smallest in right subtree
13             root->data = tmp->data;
14             root->rchild = DeleteNode(root->rchild, tmp->data); //delete smallest in right subtree
15         }
16         else { //has one child or no child
17             tmp = root;
18             if (root->lchild == NULL)
19                 root = root->rchild;
20             else if (root->rchild == NULL)
21                 root = root->lchild;
22             free(tmp);
23         }
24     }
25     return root;
26 }

8.刪除整棵樹
用後序遍歷的方式,不應該用先序遍歷或者中序遍歷,否則子樹還未被刪除時根節點就被刪除了。

1 void DeleteTree(Tnode *root)
2 {
3     if (root == NULL)
4         return;
5     DeleteTree(root->lchild);
6     DeleteTree(root->rchild);
7     root->lchild = root->rchild = NULL;
8     free(root);
9 }



寫給自己看的二叉查找樹(1):基本操作