查找-从二分查找到二叉排序树

在很多时候,我们需要对数据进行查找。这个时候有些人可能就会想到用二分法来查找数据。但是我们大家都知道,使用二分查找的一个重要前提就是我们的数据需要是有序的。这无疑会增加我们的工作量(需要我们额外进行排序再查找)。

这个时候我们就需要借助二叉排序树来帮助我们快速查找元素了。

注意:这里的代码只做演示用 没有非常严谨 还请各位不要太过于纠结这点

注意:这里的代码只做演示用 没有非常严谨 还请各位不要太过于纠结这点

注意:这里的代码只做演示用 没有非常严谨 还请各位不要太过于纠结这点

基本概念

二叉排序树(Binary Search Tree , BST)英文翻译为二叉搜索树。但是因为很多人和教科书上都用二叉排序树,所以我们也叫他二叉排序树

对于一棵二叉搜索树来说,他拥有以下的基本定义:

1.一棵二叉搜索树必须满足其为二叉树(这不是废话吗)。

2.对于二叉搜索树的任一节点来说,都满足 左孩子值 < 本节点值 < 右孩子值。

满足这两条性质的我们就将其命名为二叉搜索树。

二叉搜索树还有许多重要的性质,其中最重要的一点为:对一棵二叉搜索树进行中序遍历,遍历结果为递增。(就是中序遍历以后发现输出值是从小到大的)

这是二叉搜索树最重要的性质(个人认为)

以下就是一棵二叉搜索树:(网上找的图 应该不会侵权吧)

我们在对这棵二叉搜索树进行中序遍历就会得到以下结果:

2 5 9 12 15 16 17 18 19

了解完他的基本性质和特点后,我们开始学习如何对二叉搜索树进行增删查改

二叉搜索树的创建

创建二叉搜索树其实和创建二叉树没有什么本质上的区别,这里我直接放代码

cpp 复制代码
struct BSTNode{
    int data;
    struct BSTNode *left;
    struct BSTNode *right;
    struct BSTNode *parent; //这里看需要是否加入父节点指针
};

二叉搜索树的搜索

二叉搜索树毕竟叫搜索树,快速查找搜索元素是他最重要的功能之一

由于二叉搜索树中 左子树值 < 节点值 < 右子树值 所以我们可以利用这一性质快速进行元素查找。

cpp 复制代码
BSTNode* Search(BSTNode **tree , int val){
    BSTNode *curr = *tree;
    while(curr != NULL){
        //当前值大于节点值 向右找
        //当前值小于节点值 向左找
        //当前值等于节点值 返回节点指针
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr -> data) return curr;
    }
    //没找到
    return NULL;
}

我们可以发现 二叉搜索树的搜索效率和二分搜索是一样的 本质上都是对数据进行二分查找。

二叉搜索树的插入

由于二叉搜索树要满足 左子树值 < 节点值 < 右子树值 所以我们要保证我们在插入元素后此性质依然满足,故我们先查找后插入

cpp 复制代码
void Insert(BSTNode **tree , int val){
    //创建一个新节点
    BSTNode *newnode = (BSTNode*)malloc(sizeof(BSTNode));
    newnode -> data = val;
    newnode -> left = newnode -> right = newnode -> parent;

    //空树直接放入
    if((*tree) == NULL){
        (*tree) = newnode;
        return;
    }

    //查找我们要插入的节点的位置 如果已经存在就直接返回
    BSTNode *curr = *tree , *parent = NULL;
    while(curr != NULL){
        parent = curr;
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr -> data) return;
    }

    //找到合适的位置进行插入
    if(val > parent -> data){
        parent -> right = newnode;
        newnode -> parent = parent;
    }else if(val < parent -> data){
        parent -> left = newnode;
        newnode -> parent = parent;
    }
}

有人可能疑惑为什么必须这样插入,我也解释不清楚,但是你可以这样理解,在这样插入后,我们可以保证这棵树依旧为二叉搜索树,所以可以这样插入(即通过二叉搜索树的规则保证其唯一性,我们也可以用数学归纳法来验证)。如果你还是认为这样的解释无法接受,需要一个严谨的解释的话,你可以去找找其他人的讲解。

二叉搜索树的删除

二叉搜索树的删除是所以操作里面最麻烦的一个。同样的,我们需要保证我们在删除节点后,二叉搜索树依旧要为二叉搜索树。

我们以上面的二叉搜索树为例进行分类讨论。

第一种情况

被删除的节点没有左右子树

这种情况是最简单的,我们只需要直接删除该节点就好了

cpp 复制代码
    //待删除节点没有左右子树
    if(curr -> left == NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = NULL;
            free(curr);
        }else if(parent -> right == curr){
            parent -> right = NULL;
            free(curr);
        }
    }

第二种情况

被删除的节点只有左子树

这个时候我们需要直接将该节点的左子树直接接到该节点的父节点上

即进行如下操作:

cpp 复制代码
curr -> parent -> child = curr -> leftchild
cpp 复制代码
    //待删除节点只有左子树
    if(curr -> left != NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = curr -> left;
        }else if(parent -> right == curr){
            parent -> right = curr -> left;
        }
        curr -> left -> parent = parent;
        free(curr);
        return;
    }

第三种情况

待删除节点存在右子树

只要我们的待删除节点存在右子树,为了保证二叉搜索树的完整性,我们就必须要从右子树中找到最小的节点来替代该节点值(也可以找左子树的最大值)。

cpp 复制代码
    //待删除节点有右子树
    if(curr -> right != NULL){
        BSTNode *temp = curr;
        curr = curr -> right;
        while(curr -> left != NULL) curr = curr -> left;
        temp -> data = curr;
        parent = curr -> parent;
        parent -> left = NULL;
        free(curr);
    }

这里在我们删除时存在一个小技巧,我们可以不用进行复杂的指针操作,而是直接把值给替换,然后再删除该值的节点(将值进行覆盖后,我们要删除的就变成了没有孩子的节点,巧妙地把情况三转换成情况一)

完整删除代码

cpp 复制代码
void Delete(BSTNode **tree , int val){
    
    //查找待删除节点的位置
    BSTNode *curr = *tree , *parent = NULL;
    while(curr != NULL){
        parent = curr;
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr) break;
    }

    //只有根节点
    if(parent == NULL){
        free((*tree));
        (*tree) = NULL;
        return;
    }

    //待删除节点没有左右子树
    if(curr -> left == NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = NULL;
        }else if(parent -> right == curr){
            parent -> right = NULL;
        }
        free(curr);
        return;
    }

    //待删除节点只有左子树
    if(curr -> left != NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = curr -> left;
        }else if(parent -> right == curr){
            parent -> right = curr -> left;
        }
        curr -> left -> parent = parent;
        free(curr);
        return;
    }

    //待删除节点有右子树
    if(curr -> right != NULL){
        BSTNode *temp = curr;
        curr = curr -> right;
        while(curr -> left != NULL) curr = curr -> left;
        temp -> data = curr;
        parent = curr -> parent;
        parent -> left = NULL;
        free(curr);
    }
}

总结

二叉搜索树是一个非常好用且高效的查找数据结构,通过将原来的排序后查找优化为建树后查找,很好地提高了我们的查找效率(其实查找效率也没有快多少吧?)

完整代码:

cpp 复制代码
#include<stdio.h>

typedef struct BSTNode{
    int data;
    struct BSTNode *left;
    struct BSTNode *right;
    struct BSTNode *parent; //这里看需要是否加入父节点指针
}BSTNode;

BSTNode* Search(BSTNode **tree , int val){
    BSTNode *curr = *tree;
    while(curr != NULL){
        //当前值大于节点值 向右找
        //当前值小于节点值 向左找
        //当前值等于节点值 返回节点指针
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr -> data) return curr;
    }
    //没找到
    return NULL;
}

void Insert(BSTNode **tree , int val){
    //创建一个新节点
    BSTNode *newnode = (BSTNode*)malloc(sizeof(BSTNode));
    newnode -> data = val;
    newnode -> left = newnode -> right = newnode -> parent;

    //空树直接放入
    if((*tree) == NULL){
        (*tree) = newnode;
        return;
    }

    //查找我们要插入的节点的位置 如果已经存在就直接返回
    BSTNode *curr = *tree , *parent = NULL;
    while(curr != NULL){
        parent = curr;
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr -> data) return;
    }

    //找到合适的位置进行插入
    if(val > parent -> data){
        parent -> right = newnode;
        newnode -> parent = parent;
    }else if(val < parent -> data){
        parent -> left = newnode;
        newnode -> parent = parent;
    }
}

void Delete(BSTNode **tree , int val){
    
    //查找待删除节点的位置
    BSTNode *curr = *tree , *parent = NULL;
    while(curr != NULL){
        parent = curr;
        if(val > curr -> data) curr = curr -> right;
        else if(val < curr -> data) curr = curr -> left;
        else if(val == curr) break;
    }

    //只有根节点
    if(parent == NULL){
        free((*tree));
        (*tree) = NULL;
        return;
    }

    //待删除节点没有左右子树
    if(curr -> left == NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = NULL;
        }else if(parent -> right == curr){
            parent -> right = NULL;
        }
        free(curr);
        return;
    }

    //待删除节点只有左子树
    if(curr -> left != NULL && curr -> right == NULL){
        if(parent -> left == curr){
            parent -> left = curr -> left;
        }else if(parent -> right == curr){
            parent -> right = curr -> left;
        }
        curr -> left -> parent = parent;
        free(curr);
        return;
    }

    //待删除节点有右子树
    if(curr -> right != NULL){
        BSTNode *temp = curr;
        curr = curr -> right;
        while(curr -> left != NULL) curr = curr -> left;
        temp -> data = curr;
        parent = curr -> parent;
        parent -> left = NULL;
        free(curr);
    }
}
相关推荐
风静如云1 小时前
C++(11):成员函数饰词
c++
程序猿追2 小时前
画个饼,给数据点颜色看看——在 HarmonyOS 模拟器上手搓一个饼图/环形图组件
深度学习·算法·harmonyos
郝学胜-神的一滴2 小时前
Qt 高级开发 024:QSplitter分裂器布局精讲
开发语言·c++·qt·程序人生·用户界面
QT-Neal2 小时前
C++ 内存详解
c++
晚风吹红霞2 小时前
深入浅出C++ STL:从入门到精通的核心指南
开发语言·c++
net3m332 小时前
mymalloc函数里增加memset来初始化数据 全为0,能解决一些奇怪的问题,
算法
计算机安禾2 小时前
【算法分析与设计】第43篇:空间复杂度类与Savitch定理
java·服务器·网络·数据库·算法
8Qi82 小时前
LeetCode 416:分割等和子集 —— (0-1背包)
java·算法·leetcode·动态规划·背包问题·01背包
智者知已应修善业2 小时前
【51单片机数码管驱动2位显示0-99按键3短按+1长按+10按键4短按-1长按清零,按键不影响数码管显示】2023-8-16
c++·经验分享·笔记·算法·51单片机