数据结构 - 二叉搜索树

news2024/11/25 16:45:59

目录

一、概念

二、实现 

非递归删除

递归删除

三、总结


一、概念

二叉搜索树(BST,Binary Search Tree)

也称二叉排序树,二叉查找树

二叉搜索树:一棵二叉树,可以为空,如果不为空,满足以下性质:

1. 非空左子树的所有键值小于其根节点的键值

2. 非空右子树的所有键值大于其根节点的键值

3. 左右子树都为二叉搜索树

二、实现 

//
// Created by yangzilong on 2022/10/30.
//

#ifndef STL_BINARYSEARCHTREE_H
#define STL_BINARYSEARCHTREE_H
#include<iostream>
template <typename K>
struct BSTreeNode
{
    BSTreeNode(const K& key)
        :_key(key), _left(nullptr), _right(nullptr)
    { }
    BSTreeNode<K>* _left;
    BSTreeNode<K>* _right;
    K _key;
};

template <typename K>
class BinarySearchTree {
    typedef BSTreeNode<K> Node;
private:
    Node* _root = nullptr;
public:
    ~BinarySearchTree()
    {
        _Destroy(_root);
    }
    BinarySearchTree() = default;
    BinarySearchTree(const BinarySearchTree<K>& t)
    {
        _root = _Copy(t._root);
    }
    BinarySearchTree<K>& operator=(BinarySearchTree<K> t)
    {
        std::swap(_root, t._root);
        return *this;
    }
private:
    Node* _Copy(Node* root)
    {
        if(root == nullptr)
            return nullptr;
        Node* newNode = new Node(root->_key);
        newNode->_left = _Copy(root->_left);
        newNode->_right = _Copy(root->_right);
        return newNode;
    }
    void _Destroy(Node* root)
    {
        if(root == nullptr)
            return;
        _Destroy(root->_left);
        _Destroy(root->_right);
        delete root;
    }
public:
    // 非递归
    bool Insert(const K& key) {
        // 空树
        if (_root == nullptr) {
            _root = new Node(key);
            return true;
        }
        Node *cur = _root;
        Node *parent = nullptr;
        while (cur) {
            if (cur->_key < key) {
                parent = cur;
                cur = cur->_right;
            } else if (cur->_key > key) {
                parent = cur;
                cur = cur->_left;
            } else {
                return false; // 已经存在了
            }
        }
        if (key > parent->_key) {
            parent->_right = new Node(key);
        } else {
            parent->_left = new Node(key);
        }
        return true;
    }
    bool Find(const K& key)
    {
        Node* cur = _root;
        while(cur)
        {
            if(key > cur->_key)
            {
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                cur = cur->_left;
            }
            else
            {
                return true;
            }
        }
        return false;
    }
    bool Erase(const K& key) {
        Node *cur = _root;
        Node *parent = _root;
        while (cur) {
            if (key > cur->_key) {
                parent = cur;
                cur = cur->_right;
            } else if (key < cur->_key) {
                parent = cur;
                cur = cur->_left;
            } else {
                // edition 2
                if(cur->_left == nullptr)
                {
                    if(parent->_left == cur)
                    {
                        parent->_left = cur->_right;
                        delete cur;
                    }
                    else
                    {
                        parent->_right = cur->_right;
                        delete cur;
                    }
                }
                else if(cur->_right == nullptr)
                {
                    if(parent->_left == cur)
                    {
                        parent->_left = cur->_left;
                        delete cur;
                    }
                    else
                    {
                        parent->_right = cur->_left;
                        delete cur;
                    }
                }
                else
                {
                    // 要删除结点的左右均不为空
                    // 去删除结点的右子树中找最小值,替换法删除
                    Node* min = cur->_right;
                    Node* minParent = cur;
                    while(min->_left)
                    {
                        minParent = min;
                        min = min->_left;
                    }
                    std::swap(cur->_key, min->_key);
                    if(min == minParent->_right)
                        minParent->_right = min->_right;
                    else
                        minParent->_left = min->_right;
                    delete min;
                }
                return true;
                // old
//                if (cur->_left == nullptr && cur->_right == nullptr) {
//                    // 叶子节点,直接删除
//                    if (parent->_left->_key == key) {
//                        delete parent->_left;
//                        parent->_left = nullptr;
//                    } else {
//                        delete parent->_right;
//                        parent->_right = nullptr;
//                    }
//                } else if (cur->_left != nullptr && cur->_right != nullptr) {
//                    // 找右子树的最小值,和cur交换值
//                    Node *minParent = cur;
//                    Node *min = cur->_right; // child一定不为nullptr
//                    while (min->_left) {
//                        minParent = min;
//                        min = min->_left;
//                    }
//                    std::swap(cur->_key, min->_key);
//                    if (minParent == cur) {
//                        minParent->_right = min->_right;  // 此时child->_left一定为nullptr
//                        delete min;
//                    } else {
//                        // 此时child和parent_2的关系一定是左子树和父节点
//                        minParent->_left = min->_right;
//                        delete min;
//                    }
//                } else {
//                    Node *child = nullptr;
//                    if (cur->_right != nullptr) {
//                        child = cur->_right;
//                    } else {
//                        child = cur->_left;
//                    }
//                    if (parent->_left != nullptr && parent->_left->_key == key) {
//                        delete parent->_left;
//                        parent->_left = child;
//                    } else {
//                        delete parent->_right;
//                        parent->_right = child;
//                    }
//                }
//                return true;
            }
        }
        return false;
    }
    void InOrder()
    {
        _InOrder(_root);
        std::cout<<std::endl;
    }
private:
    void _InOrder(Node* root)
    {
        if(root == nullptr)
            return;
        _InOrder(root->_left);
        std::cout<<root->_key<<" ";
        _InOrder(root->_right);
    }
    
    // 递归
public:
    bool Find_R(const K& key)
    {
        // 最多找h次,h为树的高度。
        return _Find_R(_root, key);
    }
    bool Insert_R(const K& key)
    {
        return _Insert_R(_root, key);
    }
    bool Erase_R(const K& key)
    {
        return _Erase_R(_root, key);
    }
private:
    bool _Erase_R(Node*& root, const K& key)
    {
        if(root == nullptr)
        {
            // 不存在
            return false;
        }
        if(key < root->_key)
        {
            return _Erase_R(root->_left, key);
        }
        else if(key > root->_key)
        {
            return _Erase_R(root->_right, key);
        }
        else
        {
            // 要删除的就是这个root,这个root实际上是父节点结构体里的right or left指针的别名!!!!!
            if(root->_left == nullptr) {
                Node* del = root;
                root = root->_right;
                delete del;
            }
            else if(root->_right == nullptr) {
                Node *del = root;
                root = root->_left;
                delete del;
            }
            else
            {
                Node* minParent = root;
                Node* min = root->_right;
                while(min->_left)
                {
                    minParent = min;
                    min = min->_left;
                }
                std::swap(root->_key, min->_key);
                // 上方交换时,root的key一定比min的key小,因为min在root的右子树中。
                // 此时交换完,root的key在右子树中一定符合二叉搜索树。
                // 并且下方递归调用时,一定会走左为空的情况。
                return _Erase_R(root->_right, key);
//                if(minParent->_left == min)
//                {
//                    minParent->_left = min->_right;
//                }
//                else
//                {
//                    minParent->_right = min->_right;
//                }
//                delete min;
            }
            return true;
        }
    }
    bool _Insert_R(Node*& root, const K& key)
    {
        if(root == nullptr)
        {
            root = new Node(key);
            return true;
        }
        if(key < root->_key)
        {
            // 这里是把结构体里的指针成员传过去,参数用引用接收,改变参数就是改变这里结构体的指针成员。
            return _Insert_R(root->_left, key);
        }
        else if(key > root->_key)
        {
            // 这里是把结构体里的指针成员传过去,参数用引用接收,改变参数就是改变这里结构体的指针成员。
            return _Insert_R(root->_right, key);
        }
        else
        {
            return false;
        }
    }
//    bool _Insert_R(Node* root, const K& key)
//    {
//        if(root == nullptr)
//        {
//            _root = new Node(key);
//            return true;
//        }
//        if(root->_key < key && root->_right == nullptr)
//        {
//            root->_right = new Node(key);
//            return true;
//        }
//        else if(root->_key > key && root->_left == nullptr)
//        {
//            root->_left = new Node(key);
//            return true;
//        }
//        else if(root->_key > key)
//        {
//            return _Insert_R(root->_left, key);
//        }
//        else if(root->_key < key)
//        {
//            return _Insert_R(root->_right, key);
//        }
//        else
//        {
//            return false;
//        }
//    }
    bool _Find_R(Node* root, const K& key)
    {
        if(root == nullptr)
            return false;
        if(root->_key > key)
        {
            return _Find_R(root->_left, key);
        }
        else if(root->_key < key)
        {
            return _Find_R(root->_right, key);
        }
        else
        {
            return true;
        }
    }
};

以上包含二叉搜索树的递归版与非递归版的插入,删除,查找。以及拷贝构造和析构的实现。

唯一值得注意的就是删除了

非递归删除

先找到该结点,同时注意要记录要删除结点的父节点。

分情况:

1. 要删除结点的左右为空,即叶子结点

2. 要删除结点的左为空

3. 要删除结点的右为空

4. 要删除结点的左右都为空

1可以和2或3其中之一合并。

若要删除结点(cur)的左为空,则将cur的右赋值给parent的左或右(取决于cur是parent的左还是右)

若要删除结点(cur)的右为空,则将cur的左赋值给parent的左或右(取决于cur是parent的左还是右)

                    // 要删除结点的左右均不为空
                    // 去删除结点的右子树中找最小值,替换法删除
                    Node* min = cur->_right;
                    Node* minParent = cur;
                    while(min->_left)
                    {
                        minParent = min;
                        min = min->_left;
                    }
                    std::swap(cur->_key, min->_key);
                    if(min == minParent->_right)
                        minParent->_right = min->_right;
                    else
                        minParent->_left = min->_right;
                    delete min;

若要删除结点(cur)的左右均不为空,则查找cur的右子树中的最小结点(min),即图中的while循环),采用交换法,将cur的key和min的key交换(此时min的值放在cur的位置是符合二叉搜索树的性质的),此时要删除的结点就转换为了min。

注意,此时要判断,min是cur的右结点还是右节点的左子树的某个结点。也就是while循环有没有执行。

转换为上图,也就是

若删除3(cur),则4是min,min是minparent的左。

若删除8(cur),则10是min,min是minparent的右。

不管怎样,min的左一定为空,直接将min的右(空or非空)赋值给min的左或右即可(取决于min是minparent的左还是右)

递归删除

bool _Erase_R(Node*& root, const K& key)
    {
        if(root == nullptr)
        {
            // 不存在
            return false;
        }
        if(key < root->_key)
        {
            return _Erase_R(root->_left, key);
        }
        else if(key > root->_key)
        {
            return _Erase_R(root->_right, key);
        }
        else
        {
            // 要删除的就是这个root,这个root实际上是父节点结构体里的right or left指针的别名!!!!!
            if(root->_left == nullptr) {
                Node* del = root;
                root = root->_right;
                delete del;
            }
            else if(root->_right == nullptr) {
                Node *del = root;
                root = root->_left;
                delete del;
            }
            else
            {
                Node* minParent = root;
                Node* min = root->_right;
                while(min->_left)
                {
                    minParent = min;
                    min = min->_left;
                }
                std::swap(root->_key, min->_key);
                // 上方交换时,root的key一定比min的key小,因为min在root的右子树中。
                // 此时交换完,root的key在右子树中一定符合二叉搜索树。
                // 并且下方递归调用时,一定会走左为空的情况。
                return _Erase_R(root->_right, key);
//                if(minParent->_left == min)
//                {
//                    minParent->_left = min->_right;
//                }
//                else
//                {
//                    minParent->_right = min->_right;
//                }
//                delete min;
            }
            return true;
        }
    }

除了是基于递归实现的,这里的基本原理和非递归一样,只是在交换完cur和min的key之后,直接在cur的右子树中删除key即可(此时key在min结点中)。最终会递归到左子树为空的情况,因为min->left == nullptr

三、总结

若二叉搜索树接近完全二叉树,也就是高度接近log(N),则二叉搜索树的效率会很高。

若二叉搜索树的构建过程中,元素有序或者接近有序,则BST的查找,删除,插入的效率都会很低,接近O(N),故引出AVL树,红黑树来控制搜索树的高度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网工具viper的介绍与配置简介

文章目录0x01 介绍0x02 安装步骤一、首次安装二、更新版本三、修改密码四、反溯源配置五、关闭/重启六、安装目录介绍摘抄0x01 介绍 Viper(炫彩蛇)是提供图形化的操作界面,用户使用浏览器即可进行内网渗透. 0x02 安装步骤 一、首次安装 安装docker apt upodate apt instal…

Qt编写ffmpeg本地摄像头显示(16路本地摄像头占用3.2%CPU)

一、前言 内核ffmpeg除了支持本地文件、网络文件、各种视频流播放以外&#xff0c;还支持打开本地摄像头&#xff0c;和正常的解析流程一致&#xff0c;唯一的区别就是在avformat_open_input第三个参数传入个AVInputFormat参数&#xff0c;这个参数用于指定输入设备的格式&…

疯了!全网居然有人一次性把Java虚拟机HotSpot 给讲透彻了

Java虚拟机HotSpot HotSpot VM&#xff0c;相信大家多多少少都有所了解&#xff0c;它是目前使用范围最广的Java虚拟机&#xff0c;有着最终状态语言解释器的模板解释器。以及一直在不断迭代更新的垃圾回收器&#xff0c;还有极其超凡且精湛的即时编译器。 我认为&#xff0c…

迈动互联IBMS产品一项技术获国家专利

近日&#xff0c;迈动互联获得国家知识产权局颁发的专利证书。该专利为迈动IBMS产品应用领域的视频监控装置。近年来&#xff0c;迈动互联在IBMS可视运维平台产品持续加大研发投入&#xff0c;在IBMS领域新增9项专利&#xff0c;其中发明专利7项、实用新型2项。 迈动IBMS产品是…

centos7安装python3.7

1.安装依赖环境 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel 如果找不到wget命令&#xff0c;输入yum -y install wget安装其依赖将会被安装 2.下载python安装包…

李珣同款爱心特效代码,加DIY教程,快拿去送给你喜欢的那个ta吧。

&#x1f468;‍&#x1f393; 作者&#xff1a;bug菌 &#x1f389;简介&#xff1a;在CSDN、掘金等社区优质创作者&#xff0c;全网合计6w粉&#xff0c;对一切技术都感兴趣&#xff0c;重心偏java方向&#xff0c;目前运营公众号[猿圈奇妙屋]&#xff0c;欢迎小伙伴们的加入…

【Linux修炼手册:基本指令(上)】

目录 1 ls 指令 2 pwd命令 3 cd 指令 4 touch指令 5 mkdir指令&#xff08;重要&#xff09; 6 rmdir指令 && rm 指令&#xff08;重要&#xff09; 7 cp指令&#xff08;重要&#xff09; 8 mv指令&#xff08;重要&#xff09; 9 cat 总结&#xff1a; 1 ls…

k8s部署Skywalking及java接入agent

Skywalking由国内开源 大体架构是这样子 我用的是dockerhub的镜像 docker pull apache/skywalking-ui:8.5.0 docker pull apache/skywalking-oap-server:8.5.0-es7 docker pull elasticsearch:7.9.01.部署 搞了一个简单的es用 apiVersion: apps/v1 kind: Deployment metadat…

MySQL数据库基础:数据类型详解-文本字符串类型

前言 正好趁着这次一起学习复习一下MySQL数据库的基本知识。也是一直在用这个数据库&#xff0c;有些基础的知识点长久不用就会遗忘&#xff0c;就比如数据类型溢出的问题&#xff0c;很多时候建表的时候随便给定个类似&#xff0c;结果导入数据的时候报错又得删表就很麻烦&am…

第六章 Docker 应用部署

6-1 部署一个 SpringBoot 项目 1、将开发的 springboot 程序打成 jar 包或者 war 包&#xff1b; 2、将打好的 jar 包或 war 包上传到 Linux 某个目录下&#xff0c;比如:/root/docker 3、定义 Dockerfile 文件&#xff0c;用于创建项目镜像&#xff1b; 6-2 Docker 部署 Jar …

零基础如何学好Photoshop

1、首先第一点很重要&#xff0c;你要对PS感兴趣&#xff01; 学习好PS并不是一朝一夕就可以学好的&#xff0c;兴趣——是迈向PS大神之路的一个好的开头&#xff0c;如果你只是因为工作需要&#xff0c;被迫去学习PS&#xff0c;那么你无论请教哪位大师、报读任何培训班&…

linux篇【9】:进程间通信(共享内存)——<后序>

目录 一.system V共享内存——先让不同的进程看到同一份资源 1.共享内存原理 监控共享内存脚本 2.创建/获取 共享内存接口—shmget函数&#xff08;shared memory get&#xff09; 3.参数key解释 &#xff08;1&#xff09;共享内存存在哪里&#xff1f; &#xff08;2&a…

Spring Boot DTO 验证示例

在本教程中&#xff0c;我们将学习如何使用 Hibernate 验证器验证 Spring 启动 REST API DTO 请求。 在Java中&#xff0c;Java Bean Validation框架已经成为处理Java项目中验证的事实标准。 JSR 380 是用于 Bean 验证的 Java API 规范&#xff0c;它使用 NotNull、Min 和 Ma…

深入理解Java虚拟机

Java Virtual MachineJVM内存模型类加载器沙箱安全机制Native 和 方法区栈、队列、堆三种JVM垃圾回收一次完整的GCJVM内存模型 .class文件在进入类加载器后&#xff0c;进行加载-连接-初始化 类加载器 public class User {private String name;private Integer age;public st…

什么是浏览器的缓存机制

先来粗略的概念&#xff1a; 什么是浏览器的缓存机制 浏览器的缓存机制就是把一个请求过的web资源&#xff08;例如&#xff1a;html页面、图片、js、数据等&#xff09;拷贝一份副本储存在浏览器中&#xff1b;缓存会根据进来的请求保存输出内容的副本&#xff0c;当下一个请求…

frp内网穿透并实现开机自启动

frp配置内网穿透、ssh远程连接、systemctl自启动 1.服务器端 VPS 配置内网穿透 修改frps.ini文件&#xff1a; # frps.ini[common]bind_port 7000 启动frps&#xff1a; ./frps -c ./frps.ini 2.客户端配置 修改 frpc.ini 文件&#xff0c;假设 frps 所在服务器的公网 IP 为…

【C++笔试强训】第二十天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

初识C++(五)

简述&#xff1a;初识C章节最后一节啦 整体感觉就是C像是C的补充和升级 以一种更简单的方式奔向普罗大众 从而也能使更多人接受编程 当然不是讲C简单 就是C像是从机器时代进入了电气时代 以更简单的操作实现更高的效率&#xff0c;这是我在接触C一周时的整体印象。 目录 auto关…

学习python第6天

函数 函数的作用&#xff1a; 函数是组织好的,可以重复使用的、用来实现单一功能的代码 函数的组成   数学函数 y 6 * x 9&#xff0c;x 是自变量&#xff0c;6 * x 9 是执行过程&#xff0c;y 是因变量&#xff0c;自变量 x 决定了因变量 y 的值。 那么&#xff0c;你…

Python美化桌面—自制桌面宠物

前言 嗨嗨&#xff0c;最近就喜欢搞一些花里胡哨的东西 这不就开始折腾我的电脑了吗 浅浅搞个桌面小挂件&#xff08;桌面宠物&#xff09; 前期准备 开发工具 Python版本&#xff1a;3.6.4 相关模块&#xff1a; PyQt5模块&#xff1b; 以及一些Python自带的模块。 …