c++进阶--二叉搜索树

news2024/11/23 23:49:32

目录

前言

一、二叉搜索树

1.二叉搜索树概念

2.二叉搜索树操作

二、二叉搜索树实现

0.定义一个节点

1.定义一棵树

2.增删改查

2.1.查找

2.2.插入

2.3.删除

2.3.1非递归删除法

a.只有左孩子 -- 删除14

b.只有右孩子-- 删除10

c.有左右孩子--删除8

2.3.2递归删除法

三、二叉搜索树应用

1.K模型(解决在不在的问题)

2.KV模型

3.二叉搜索树性能分析

总结


前言

本文中出现的源码已在本地vs2019下测试无误,上传至gitee:

https://gitee.com/a_young/binary-search-tree


一、二叉搜索树

1.二叉搜索树概念

二叉搜索树又称为二叉排序树,它或者是一颗空树,或者具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 左、右子树都是二叉搜索树

2.二叉搜索树操作

int a[] = {8,3,1,10,6,4,7,14,13};

 1.二叉搜索树的查找

  1. 从根节点开始比较,查找,如果比根节点值大,往右走查找,比根节点值小,往左走查找
  2. 最多查找高度次,走到空节点还没找到,则说明这个值在树中不存在。

2.二叉搜索树的插入

  1. 若为空树,则直接新增节点,赋值给root指针
  2. 树不为空,按性质查找插入位置,插入新节点

3.二叉搜索树的删除

  • 先查找元素是否在树中,如果不存在,返回。否则要删除的节点分为下面四种情况
  1. 要删除的结点是叶子节点
  2. 要删除的节点只有左孩子节点
  3. 要删除的节点只有右孩子节点
  4. 要删除的节点有左、右孩子节点

1可以看成2,3的一种情况。

  • 只有左孩子节点(如上图 14) :删除该节点,并使被删除节点的父节点指向被删节点的左孩子节点
  • 只有右孩子节点:删除该节点,并使被删除节点的父节点指向被删节点的右孩子节点
  • 有左右孩子节点:先寻找右树的最小节点(或者左树的最大节点),用它的值填补到被删除节点中,再处理该节点的删除问题。 详细处理代码以及坑往下。

二、二叉搜索树实现

0.定义一个节点

template<class k>
struct BSTreeNode
{
    //三个成员
    BSTreeNode<T>* _left;
    BSTreeNode<T>* _right;
    K _key;

    //构造函数
    BSTreeNode(const k& key)
    :_left(nullptr)
    ,_right(nullptr)
    ,key(key)
    {
    }

};

1.定义一棵树

实现构造,拷贝构造,析构。

  • 构造的时候用一个节点,初始化为空。
  • 拷贝构造,这里注意,必须是深拷贝,浅拷贝容易出现野指针。
  • 析构的时候使用后序递归删除即可。
template<class k>
class BSTree
{
   typedef BSTreeNode<T> Node;
   public:
   /* BSTree()
    :_root(nullptr)
    {}
    */
    BSTree() = default; //指定强制生成默认构造
    //拷贝构造
    BSTree(const BSTree<K> & t)
    {
       _root = copy(t._root);
    }

    //跟前序创建类似 后序回来才链接
    Node* copy(Node * root)
    {
       if(root == nullptr)
            return nullptr;

       Node * new_root = new Node(root ->key);
       new_root ->_left = Copy(root->_left);
       nre_root->_right = Copy(root->_right);
       return new_root;
       
    }
    //析构
    ~BSTree()
    {
        //使用后序递归
       Destory(_root);
    
    }

     void Destroy(Node * root)
     {
         if(root == nullprt)
            return ;
         Destroy(root->left);
         Destroy(root->right);
         delete root;
      }

    //成员函数
    //实现增删改查 protected封装
    //赋值
    BSTree<k>& operator=(BSTree<k> t)
    {
         swap(_root,t._root);
         return *this;   
    }
   private:
    
    Node * _root = nullptr; 

2.增删改查

2.1.查找

bool Find(const k& key)
{
    Node * cur = _root;
    while(cur)
    {
        if(cur->_ley <key)
            cur = cur->right;
        else if(cur->_key >key)
            cur = cur ->right;
        else
            {
                return true;
            }

    return false;
}
  

2.2.插入

//非递归解法
bool Insert(const k& key)
{
    //如果是一个空树
    if(_root == nullptr)
      {
           _root = new Node(key);        
            return true;
      }
      
    Node * parent = nullptr;
    Node * cur = _root;
    while(cur)
    {
        if (cur->_key < key)
		{
			parent = cur;
            //注意这里,cur更新前不是空,更新后才是空,更新前还可以访问right
		    cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
        else
            return false;
    }
    
    //找到对应位置后,开始插入
    cur = new Node(key)
    //链接
    if(parent->_key < key)
        parent ->_right = cur;
    else
        parent->_left = cur;
    
    return true;
}
    
            
        

这里使用递归的方式再来插入,一个很巧妙的使用引用的例子。假设我要在上图里面插入一个2,先去找到合适的位置,1的right是空,在这里new了一个Node,值为2,同时返回,此时这里的Node就是天然的上一层栈帧中的root->right,完美链接

bool _InsertR(Node& * root, const k& key)
{
    if(root == nullptr)
    {
        root = new Node(key);
         return true;
    }
    
   	if (root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else
	{
		return false;
	}
}


bool InsertR(const k& key)
{
    return _InsertR(_root, key);
}

2.3.删除

2.3.1非递归删除法

bool _Erase(Node * root,const K& key)
{
    Node * parent = nullptr;
    Node * cur = _root;
    while(cur)
    {
        if(_cur->_key < key)
        {
            parent = cur;
            cur = cur->right;
        }

        else if(_cur->_key >key)
        {
            parent = cur;
            cur = cur->leftt;
        }

        //找到了,开始删除
        else
        {
            //a.只有左孩子
            //b.只有右孩子
            //c.左右孩子都有
            
            return true;

        }

}
bool Erase(const K& key)
{
   return  bool _Erase(Node * root,const K& key);
}

a.只有左孩子 -- 删除14

通过上面代码进行查找,找到了14,此时parent指向10,cur指向14,14只有左孩子,进行链接,14的左孩子成为10的右孩子,删除14。

if(cur ->_right  == nullptr)
 {
       if(parent ->_left == cur)
            parent ->_left = cur->left;
        
        else if(parent ->_right == cur)
            parent ->_right = cur->_right;

        delete cur;
}
        return true;
    

但是有特殊情况:如果有一棵树为只有两个节点,要删除的节点为根节点,则它的父节点parent就是空指针,无法进行判断删除。所以这里要进行判断,如果是,则更新root,再删除cur。

if(cur == _root)
{
    _root = cur->_left;
}

b.只有右孩子-- 删除10

通过查找找到了10,cur指向10,parent指向8,开始删除

if(cur == _root)
{
    _root = cur->_right;
}


else if( parent ->_left == cur)
{
        parent->left = cur->_right;
}

else if( parent ->_right == cur)
{
        parent ->right = cur->_right;
 }


delete cur;
return true;

c.有左右孩子--删除8

此时需要找到子树中的左树中的最右节点(最大)或者右数中的最左节点(最小)节点来替换掉根节点,如下我们使用右树中的最左节点去替换。 此时这个最左节点,它也可能有右孩子,肯定没有左孩子。所以我们要进行托孤,即这里要找到这个最左节点的父亲

假设删除树中的根节点8,树中10有左节点 9,9有自己的右孩子9.5,所以先找到右树的最左节点9,它的父亲10,托孤自己的右孩子9.5,为10的左孩子。9替代掉8,最后删除掉minRight这个位置的结构。

这里也需要小心避坑,如果这里删除8,minRight指向10,没有minRight->left,pminRight为空(所以pminRight不能刚开始就给空,需要给cur)。并且10只有自己的右孩子14,它要成为8的右孩子,所以托孤的时候需要判断。

/*Node* pminRight = nullptr;
Node * minRight = cur->_right;
while(minRight->_left)
{
    pminRight = minRight;
    minRight = minRight->left;
}

    cur ->_key = minRight ->_key;
    pminRight ->left = minRight->_right;

    delete minRight;
 */ 


Node * pminRight = cur;
Node * minRight = cur->_right;
while(minRight->_left)
{
    pminRight = minRight;
    minRight = minRight ->left;
}

    cur ->_key = minRight->_key;
    //进行托孤
    if(pminRight->left == minRight->right)
        pminRight->left = minRight->right;
    else if(pminRight->right == minRight->left)
        pminRight->right = minRight->right;

    delete minRight;

                                          

2.3.2递归删除法

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
	 return false;

	if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}
	else
	{
		Node* del = root;

		// 开始准备删除
		if (root->_right == nullptr)
		{
		    root = root->_left;
		}
		else if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else
        {
            //这里找左树的最右节点
            Node * maxleft = root->_left;
            while(maxleft->_right)
            {
                maxleft = maxleft ->right;
            }

 
            swap(root->_key, maxleft->_key);
            return _EraseR(root->_left,key);
          }
        delete del;
        return true;
}

三、二叉搜索树应用

1.K模型(解决在不在的问题)

K模型只有key作为关键字,结构中只存储key即可,关键码即为需要搜索到的值。

  • 比如门禁系统:芯片中有个人信息,根据个人信息在数据库中查找,如果在,就通过。

2.KV模型

每一个关键码key,都有与之对应的值value,即<key,value>的键值对,该方式在生活中非常常见

  • 比如英汉词典就是中文与英文的对应关系,通过英文可以快速找到与之对应的中文,英文单词与其对应的中文<word,chinese>构成一种键值对。
  • 统计单词次数,统计结束,给定单词就可以快速找到出现的次数。<key,count>构成一种键值对。                
  • //改造二叉搜索树为kv结构
    template<class k, class v>
    struct BSTNode
    {
        BSTNode(const k& key = k(), const v&value = v())
        :_pLeft(nullptr),_pRight(nullptr),_key(key),_value(vaule)
        {}
    
        BSTNode<T> * _pleft;
        BSTNode<T> * _pright;
        k _key;
        v _value;
    };
    
    template<class k, class v>
    class BSTree
    {
        typedef BSTNode<k,v> Node;
        typedef Node* pNode;
        public:
            BSTree(): _pRoot(nullptr){}
            pNode Find(const K& key);
            bool Insert(const k& key ,const v& value);
            bool Erase(const k& key);
        private:
            pNode _pRoot;
    }
    
    void TestBSTree()
    {
     // 输入单词,查找单词对应的中文翻译
     BSTree<string, string> dict;
     dict.Insert("string", "字符串");
     dict.Insert("tree", "树");
     dict.Insert("left", "左边、剩余");
     dict.Insert("right", "右边");
     dict.Insert("sort", "排序");
     // 插入词库中所有单词
     string str;
     while (cin>>str)
     {
     BSTreeNode<string, string>* ret = dict.Find(str);
     if (ret == nullptr)
     {
     cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
     }
     else
     {
     cout << str << "中文翻译:" << ret->_value << endl;
     }
     }
    }
    void TestBSTree()
    {
     // 统计水果出现的次数
     string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
    "苹果", "香蕉", "苹果", "香蕉" };
     BSTree<string, int> countTree;
     for (const auto& str : arr)
     {
     // 先查找水果在不在搜索树中
     // 1、不在,说明水果第一次出现,则插入<水果, 1>
     // 2、在,则查找到的节点中水果对应的次数++
     //BSTreeNode<string, int>* ret = countTree.Find(str);
     auto ret = countTree.Find(str);
     if (ret == NULL)
     {
     countTree.Insert(str, 1);
     }
     else
     {
     ret->_value++;
     }
     }
     countTree.InOrder();
    }
                                                              

3.二叉搜索树性能分析

插入和删除都必须先查找,则查找的效率代表了二叉搜索树中各个操作的性能。

对于有n个节点的二叉搜索树,若每个元素查找概率相等,查找查毒是二叉树的深度函数,节点越多,比较次数越多。

最优:完全二叉树,O(logN)

最差:单支O(N)

如果退化为单支,二叉搜索树性能很差,所以使用AVL树和红黑树,后续文章继续介绍。

 


总结

本文主要介绍了二叉搜索树实现以及应用,技术有限,如有错误请指正。

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

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

相关文章

【论文总结】Chatting with GPT-3 for Zero-Shot Human-Like Mobile Automated GUI Testing

与GPT-3对话进行零尝试人类化移动自动化GUI测试 摘要&#xff1a; 移动应用在人们的日常生活中变得不可或缺&#xff0c;而自动化图形用户界面&#xff08;Graphical User Interface&#xff0c;GUI&#xff09;测试广泛用于应用程序质量保证。对于自动化GUI测试&#xff0c;越…

rv1126更新rknpu驱动教学

测试平台&#xff1a;易佰纳rv1126 38板 查看板端版本-------------------------------------------------- 1&#xff1a;查看npu驱动版本 dmesg | grep -i galcore&#xff0c;可以看到版本为6.4.3.5 2&#xff1a;查看rknn-server版本 strings /usr/bin/rknn_server | g…

新能源电动车充电桩控制主板的材料选择

新能源电动车充电桩控制主板的材料选择 你是否想过&#xff0c;汽车充电桩控制主板的材料选择竟然还有这么多讲究?不再只是简单的充电问题&#xff0c;而是涉及到耐高温、耐高压、耐腐蚀、耐潮湿、耐油污、低热耗等方方面面。 首先&#xff0c;安全性是材料选择的首要考虑因素…

mysql自增主键不连续情况分析

1.唯一键冲突 比如increnment_test中已经存在了co1为3的记录,当再插入col1为3的记录时,就会出现主键不唯一错误,但此时自增主键已经1,所以会发生主键不连续情况 DROP TABLE IF EXISTS increnment_test; CREATE TABLE increnment_test (id int(0) NOT NULL AUTO_INCREMENT,col…

svg使用技巧

什么是svg SVG 是一种基于 XML 语法的图像格式&#xff0c;全称是可缩放矢量图&#xff08;Scalable Vector Graphics&#xff09;。其他图像格式都是基于像素处理的&#xff0c;SVG 则是属于对图像的形状描述&#xff0c;所以它本质上是文本文件&#xff0c;体积较小&#xf…

Python-OpenCV中的图像处理

Python-OpenCV中的图像处理 颜色空间转换物体跟踪获取HSV的值几何变换图像缩放图像平移图像旋转仿射变换透视变换 图像阈值单阈值自适应阈值Otsus二值化 颜色空间转换 在 OpenCV 中有超过 150 中进行颜色空间转换的方法。但是你以后就会 发现我们经常用到的也就两种&#xff1…

ZMQ发布订阅模式二次封装

ZeroMQ 参考ZMQ从入门到掌握一 ZeroMQ是一种基于消息队列的多线程网络库&#xff0c;其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象&#xff0c;提供跨越多种传输协议的套接字。ZeroMQ 并不是一个对 socket 的封装&#xff0c;不能用它去实现已有的网络协议。它有…

加密、解密、编码

urlencode urlencode_百度百科 Base64是一种二进制到文本的编码方式&#xff0c;而且编码出的字符串只包含ASCII基础字符 下图是Base64码表&#xff0c;可以看到从0到63的每个数字都对应一个上面的一个字符。 文件-base64字符串互转 sun.misc包中的类 try (FileOutputStre…

Node.Js安装与配置教程

目录 1.下载官网 2.选择安装路径 3.添加环境变量 4.验证是否安装成功 5.修改模块下载位置 (1)查看npm默认存放位置 6.在node.js安装目录下&#xff0c;创建两个文件夹 7.修改默认文件夹 8.测试默认位置是否更改成功 9.安装报错解决办法 10.路径未更改成功解决办法 …

MIT 6.830数据库系统 -- lab six

MIT 6.830数据库系统 -- lab six 项目拉取引言steal/no-force策略redo log与undo log日志格式和检查点 开始回滚练习1&#xff1a;LogFile.rollback() 恢复练习2&#xff1a;LogFile.recover() 测试结果疑问点分析 项目拉取 原项目使用ant进行项目构建&#xff0c;我已经更改为…

【uniapp 小程序开发页面篇】代码编写规范 | 页面编写规范 | 小程序API

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; uni-app | 小程序开发 开发工具&#xff1a;HBuilderX 小程序开发页面篇 小程序组件规范小程序介绍小程序规范代码编写规范须遵循的开发规范 运行特性编译器选择编译规则工程目录结构static目录 使用注意static目录 条件…

Spring-2-透彻理解Spring 注解方式创建Bean--IOC

今日目标 学习使用XML配置第三方Bean 掌握纯注解开发定义Bean对象 掌握纯注解开发IOC模式 1. 第三方资源配置管理 说明&#xff1a;以管理DataSource连接池对象为例讲解第三方资源配置管理 1.1 XML管理Druid连接池(第三方Bean)对象【重点】 数据库准备 -- 创建数据库 create …

Easys Excel的表格导入(读)导出(写)-----java

一,EasyExcel官网: 可以学习一些新知识: EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 二,为什么要使用easyexcle excel的一些优点和缺点 java解析excel的框架有很多 &#xff1a; poi jxl,存在问题&#xff1a;非常的消耗内存&#xff0c; easyexcel 我们…

使用TDOSCommand调用Powershell脚本对进程进行操作

列出当前运行的进程&#xff1a; varPowerShellPath, ScriptPath, CommandLine: string; beginMemo6.Clear;PowerShellPath : powershell.exe ; // 假设 PowerShell 可执行文件在系统环境变量中// 构造命令行参数CommandLine : Get-Process | Select-Object Name,Id;// 设置命…

【Linux】总结2-进程篇1

文章目录 冯诺伊曼结构操作系统什么是程序&#xff1f;什么是进程&#xff1f;操作系统是如何来管理进程的&#xff1f;PCB&#xff08;struct task_struct{...}&#xff09; 冯诺伊曼结构 冯诺依曼提出了计算机制造的三个基本原则&#xff0c;即采用二进制逻辑、程序存储执行…

Stable Diffusion - 常用的负向提示 Embeddings 解析与 坐姿 (Sitting) 提示词

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132145248 负向 Embeddings 是用于提高 StableDiffusion 生成图像质量的技术&#xff0c;可以避免生成一些不符合预期的图像特征&#xff0c;比如…

day5gdb调试模式和makefile

一、gdb调试 1.1gdb调试的作用 gdb调试检查的是逻辑错误&#xff0c;而非语法错误 1.2gdb流程 1、gcc -g 1.c ---->加-g参数的作用&#xff0c;生成可以调试的gdb文件 2、gdb 可执行文件名/a.out ---->进入gdb工具进行调试 3、输入l&#xff0c;带行号打印文件信息…

管理类联考——逻辑——论证逻辑——汇总篇——目录+提炼

文章目录 一、削弱方法关系的削弱必要方法的削弱因果推理的削弱果因推理的削弱概念跳跃的削弱数量比例的削弱比例因果的削弱 二、支持方法关系的支持必要方法的支持因果推理的支持果因推理的支持概念跳跃的支持数量比例的支持比例因果的支持 三、假设方法关系的假设必要方法的假…

不分股权不分管理,只分利润:共享模式的新零售布局

实体行业如何通过共享模式去整合那些有资源的人&#xff0c;来完成新零售的一个布局&#xff1f;比如对于餐饮行业而言&#xff0c;一样的资源&#xff0c;经常有用餐、聚餐需求的人是谁&#xff1f; 有商会组织者、公司的管理层、培训机构、社群群主等等。那么如何把这些人整…

记一次Linux启动Mysql异常解决

文章目录 第一步&#xff1a; netstat -ntlp 查看端口情况2、启动Mysql3、查看MySQL日志 tail -100f /var/log/mysqld.log4、查看磁盘占用情况&#xff1a;df -h5、思路小结 第一步&#xff1a; netstat -ntlp 查看端口情况 并没有发现3306数据库端口 2、启动Mysql service …