寫給自己看的二叉查找樹(1):基本操作
搬運自我的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):基本操作