二叉搜索树
目录:
- 前言
- 特性
- 基本框架
- 插入节点
- 删除节点
- 整体代码
- 总结
1、前言
我们见过了基本的二叉树,其可以是这样的:
由于其没有了基本的规则,所以其的左右子树可以是任意的节点,这导致我们假如要寻找其中的一个节点的话,就可能需要将整棵树都遍历了才能找到节点,就像这里我们假如要找到6这个节点,则需要一直遍历这棵树直到最后才找到了这个节点。
所以过去就有人在想如何将这棵树变得有规律,便于在日常中的使用呢?
因此,我们的二叉搜索树就出现了,假如把上面这棵普通的树变为一棵二叉搜索树,其可以是这样的:
这样对比上面的普通的二叉树,这棵树就看着舒服多了。
2、特性
那下面就来先介绍一下这种特殊的二叉树的特性:
将比根节点小的放到其左子树上,反之,则放到其右子树上
中序遍历得到的数列是一个升序的数列
(1)、特性一
我们可以先继续拿上面的这棵树来进行讲解:
对于这棵树,我们可以轻易的发现,比根节点小的在其左子树上,而比它大的则在右子树上。
借着这个特性,我们自己也可以来试试将几个节点串成一棵二叉搜索树:
那么我们就可以对这些节点进行一个连接的操作,得到的结果有很多种,这取决于选择节点的顺序,我这就以从左往右的顺序来展示:
这里我们就可以看到这棵树的大概形状,也满足我们的特性一。
(2)、特性二
对于特性二我们这里就直接拿上面的这两棵树来说明。
对于树一:
中序遍历的结果:3 4 5 6
满足了我们的特性二。
对于树二:
中序遍历的结果:5 10 15 20 25
也满足了我们的特性二。
3、基本框架
这里我们就用C语言来实现这整棵树。
(1)、树的定义
//树的节点
typedef struct TreeNode
{
int val;//值域
struct TreeNode* left;//左子树
struct TreeNode* right;//右子树
}TreeNode;
(2)、先序遍历和中序遍历
先序遍历和中序遍历可以确定一棵唯一的树,这就是需要遍历的原因
//先序遍历
void preTraversal(TreeNode* root)
{
if(root == NULL) return;
printf("%d ", root->val);
preTraversal(root->left);
preTraversal(root->right);
}
//中序遍历
void midTraversal(TreeNode* root)
{
if(root == NULL) return;
midTraversal(root->left);
printf("%d ",root->val);
midTraversal(root->right);
}
(3)、销毁整棵树
//销毁函数
void destroy(TreeNode** proot)
{
//后序遍历销毁树
if(*proot == NULL) return;
destroy(&((*proot)->left));
destroy(&((*proot)->right));
free(*proot);
*proot = NULL;
}
4、插入
//插入函数:根据二叉搜索树的性质
TreeNode* insert(TreeNode* root, int key)
{
//有两种方法:一种是递归,一种是迭代,这里我统一采用采用递归
if(root == NULL)
{
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->left = NULL;
newNode->right = NULL;
newNode->val = key;
return newNode;
}
//根据二叉搜索树的性质
if(root->val > key)
root->left = insert(root->left, key);
else if(root->val < key)
root->right = insert(root->right, key);
else if(root->val == key)
return NULL;//不能同时出现两个相同的节点
return root;
}
5、删除节点
对于删除节点我们可以分为这几种情况:
(1)、删除的节点没有子树
像这里,我们假如把20这个节点删除的话,可以得到:
发现这棵树的特性没有遭到破坏,所以直接删除即可。
(2)、删除的节点只有左子树或右子树
首先我们还是这棵树:
假如我们要求删除15这个节点,对于这种情况,处理方法也比较简单:
就是删除15这个节点后,然后让其子树直接取代到原本的位置上
效果如下:
我们经过简单的观察就可以发现其特性没有被破坏。
(3)、删除的节点同时有左子树和右子树
我们可以首先看到一个例子:
假如,我们要删除的是15这个节点,那么我应该把哪个节点替换上来来保证特性二不被破坏呢?
直接说结论:把15这个节点的左子树接到右子树最左边的左子树,然后将原本的节点的右子树替上来
删除后的结果如下:
这个很明显满足二叉搜索树的所有特性。
代码实现
//删除节点:根据二叉搜索树的性质
TreeNode* Delete(TreeNode* root, int key)
{
if(root == NULL)
{
printf("没有这样的节点\n");
return root;
}
if(root->val > key)
{
root->left = Delete(root->left,key);
}
else if(root->val < key)
{
root->right = Delete(root->right,key);
}
else if(root->val == key)
{
//情况一:
if(root->left == NULL && root->right == NULL)
{
free(root);
root = NULL;
return root;
}
//情况二:
else if(root->left && root->right == NULL)
{
TreeNode* left = root->left;
free(root);
return left;
}
else if(root->right && root->left == NULL)
{
TreeNode* right = root->right;
free(root);
return right;
}
//情况三:
else if(root->right && root->left)
{
TreeNode* cur = root->right;
TreeNode* left = root->left;
TreeNode* right = root->right;
while(cur->left) cur = cur->left;
cur->left = left;
free(root);
root = right;
return root;
}
}
return root;
}
6、整体代码
//树的节点
typedef struct TreeNode
{
int val;//值域
struct TreeNode* left;//左子树
struct TreeNode* right;//右子树
}TreeNode;
//先序遍历
void preTraversal(TreeNode* root)
{
if(root == NULL) return;
printf("%d ", root->val);
preTraversal(root->left);
preTraversal(root->right);
}
//中序遍历
void midTraversal(TreeNode* root)
{
if(root == NULL) return;
midTraversal(root->left);
printf("%d ",root->val);
midTraversal(root->right);
}
//销毁函数
void destroy(TreeNode** proot)
{
//后序遍历销毁树
if(*proot == NULL) return;
destroy(&((*proot)->left));
destroy(&((*proot)->right));
free(*proot);
*proot = NULL;
}
//插入函数:根据二叉搜索树的性质
TreeNode* insert(TreeNode* root, int key)
{
//有两种方法:一种是递归,一种是迭代,这里我统一采用采用递归
if(root == NULL)
{
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->left = NULL;
newNode->right = NULL;
newNode->val = key;
return newNode;
}
//根据二叉搜索树的性质
if(root->val > key)
root->left = insert(root->left, key);
else if(root->val < key)
root->right = insert(root->right, key);
else if(root->val == key)
return NULL;//不能同时出现两个相同的节点
return root;
}
//删除节点:根据二叉搜索树的性质
TreeNode* Delete(TreeNode* root, int key)
{
if(root == NULL)
{
printf("没有这样的节点\n");
return root;
}
if(root->val > key)
{
root->left = Delete(root->left,key);
}
else if(root->val < key)
{
root->right = Delete(root->right,key);
}
else if(root->val == key)
{
//情况一:
if(root->left == NULL && root->right == NULL)
{
free(root);
root = NULL;
return root;
}
//情况二:
else if(root->left && root->right == NULL)
{
TreeNode* left = root->left;
free(root);
return left;
}
else if(root->right && root->left == NULL)
{
TreeNode* right = root->right;
free(root);
return right;
}
//情况三:
else if(root->right && root->left)
{
TreeNode* cur = root->right;
TreeNode* left = root->left;
TreeNode* right = root->right;
while(cur->left) cur = cur->left;
cur->left = left;
free(root);
root = right;
return root;
}
}
return root;
}
//测试二叉搜索树
void testBST()
{
TreeNode* root = NULL;
root = insert(root, 4);
root = insert(root, 3);
root = insert(root, 5);
root = insert(root, 6);
printf("先序遍历:");
preTraversal(root);
printf("\n");
printf("中序遍历:");
midTraversal(root);
printf("\n");
root = Delete(root, 0);
root = Delete(root, 5);
printf("先序遍历:");
preTraversal(root);
printf("\n");
printf("中序遍历:");
midTraversal(root);
printf("\n");
destroy(&root);
}
int main()
{
testBST();
return;
}
输出结果:
符合我们的预期结果,所以也就说明了我们的二叉搜索树建立成功了。
7、总结
二叉搜索树很好理解,实现起来也比较简单,就是要注意删除的时候的那几种情况,把这些细节处理好了就可以了,那么期待我们的在下一站的相遇!!!