一起来学学简单的特殊的二叉树吧!

39 阅读6分钟

二叉搜索树

目录:

  1. 前言
  2. 特性
  3. 基本框架
  4. 插入节点
  5. 删除节点
  6. 整体代码
  7. 总结

1、前言

我们见过了基本的二叉树,其可以是这样的:

屏幕截图 2024-12-06 155132.png

由于其没有了基本的规则,所以其的左右子树可以是任意的节点,这导致我们假如要寻找其中的一个节点的话,就可能需要将整棵树都遍历了才能找到节点,就像这里我们假如要找到6这个节点,则需要一直遍历这棵树直到最后才找到了这个节点。

所以过去就有人在想如何将这棵树变得有规律,便于在日常中的使用呢?

因此,我们的二叉搜索树就出现了,假如把上面这棵普通的树变为一棵二叉搜索树,其可以是这样的:

屏幕截图 2024-12-06 155736.png

这样对比上面的普通的二叉树,这棵树就看着舒服多了。

2、特性

那下面就来先介绍一下这种特殊的二叉树的特性:

  1. 将比根节点小的放到其左子树上,反之,则放到其右子树上

  2. 中序遍历得到的数列是一个升序的数列

(1)、特性一

我们可以先继续拿上面的这棵树来进行讲解:

屏幕截图 2024-12-06 155736.png

对于这棵树,我们可以轻易的发现,比根节点小的在其左子树上,而比它大的则在右子树上。

借着这个特性,我们自己也可以来试试将几个节点串成一棵二叉搜索树:

屏幕截图 2024-12-06 160512.png

那么我们就可以对这些节点进行一个连接的操作,得到的结果有很多种,这取决于选择节点的顺序,我这就以从左往右的顺序来展示:

屏幕截图 2024-12-06 161056.png

这里我们就可以看到这棵树的大概形状,也满足我们的特性一。

(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)、删除的节点没有子树

屏幕截图 2024-12-06 164646.png

像这里,我们假如把20这个节点删除的话,可以得到:

屏幕截图 2024-12-06 164743.png

发现这棵树的特性没有遭到破坏,所以直接删除即可。

(2)、删除的节点只有左子树或右子树

首先我们还是这棵树:

屏幕截图 2024-12-06 164646.png

假如我们要求删除15这个节点,对于这种情况,处理方法也比较简单:

就是删除15这个节点后,然后让其子树直接取代到原本的位置上

效果如下:

屏幕截图 2024-12-06 165207.png

我们经过简单的观察就可以发现其特性没有被破坏。

(3)、删除的节点同时有左子树和右子树

我们可以首先看到一个例子:

屏幕截图 2024-12-06 170655.png

假如,我们要删除的是15这个节点,那么我应该把哪个节点替换上来来保证特性二不被破坏呢?

直接说结论:把15这个节点的左子树接到右子树最左边的左子树,然后将原本的节点的右子树替上来

删除后的结果如下:

屏幕截图 2024-12-06 170913.png

这个很明显满足二叉搜索树的所有特性。

代码实现

//删除节点:根据二叉搜索树的性质
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;
}

输出结果:

屏幕截图 2024-12-06 185628.png

符合我们的预期结果,所以也就说明了我们的二叉搜索树建立成功了。

7、总结

二叉搜索树很好理解,实现起来也比较简单,就是要注意删除的时候的那几种情况,把这些细节处理好了就可以了,那么期待我们的在下一站的相遇!!!