写给自己看的二叉查找树:基本操作

Posted lyrich

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了写给自己看的二叉查找树:基本操作相关的知识,希望对你有一定的参考价值。

搬运自我的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 }

 



以上是关于写给自己看的二叉查找树:基本操作的主要内容,如果未能解决你的问题,请参考以下文章

算法-不同的二叉查找树I和II(动态规划和深搜算法)

二叉查找树简单实现

红黑树——一个自平衡的二叉搜索树

红黑树(更高级的二叉查找树)

数据结构:查找|| 平衡二叉树

163. 不同的二叉查找树