c++进阶--二叉搜索树模拟实现

news2024/11/19 20:40:08

目录

前言

一、二叉搜索树

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/851809.html

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

相关文章

停车场收费系统ssm车辆车库管理jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 停车场收费系统 一个基于ssm框架的小系统 后端&…

计算机三级网络技术(持续更新)

BGP考点 A S&#xff1a;自治系统 BGP: Border Gateway Protocol&#xff08;当前使用的版本是 BGP-4&#xff09;外部网关协议 动态路由协议可以按照工作范围分为IGP以及EGP。IGP工作在同一个AS内&#xff0c;主要用来发现和计算路由&#xff0c;为AS内提供路由信息的交换&…

基于大数据为底层好用准确性高的竞彩足球比分预测进球数分析软件介绍推荐

大数据与贝叶斯理论在足球比赛分析与预测中的应用 随着科技的不断进步&#xff0c;大数据分析在各个领域的应用也越来越广泛&#xff0c;其中包括体育竞技。足球比赛作为全球最受欢迎的运动之一&#xff0c;也借助大数据和贝叶斯理论来进行模型分析和预测。本文将通过结合贝叶…

Java笔记(三十):MySQL(上)-- 数据库、MySQL常用数据类型、DDL、DML、多表设计

一、数据库 0、MySQL安装&#xff0c;IDEA配置MySQL 用MySQL installer for windows&#xff08;msi&#xff09;MySQL默认安装位置&#xff1a;C:\Program Files\MySQL\MySQL Server 8.0配置环境变量使用前先确保启动了mysql服务my.ini位置&#xff1a;C:\ProgramData\MySQL…

交替方向乘子

目录 一&#xff0c;交替方向乘子ADMM 1&#xff0c;带线性约束的分离优化模型 2&#xff0c;常见优化模型转带线性约束的分离优化模型 3&#xff0c;带线性约束的分离优化模型求解 4&#xff0c;交替方向乘子ADMM 本文部分内容来自教材 一&#xff0c;交替方向乘子ADMM …

初中信息技术考试编程题,初中信息技术python教案

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;初中信息技术python编程题库 网盘&#xff0c;初中信息技术python编程教学&#xff0c;今天让我们一起来看看吧&#xff01; ID:12450455 资源大小&#xff1a;934KB 资料简介: 2019-2020学年初中信息技术【轻松备课】P…

5基础篇:自定义日志

前言 在所有的后端服务中,日志是必不可少的一个关键环节,毕竟日常中我们不可能随时盯着控制台,问题的出现也会有随机性、不可预见性。一旦出现问题,要追踪错误以及解决,需要知道错误发生的原因、时间等细节信息。 之前的需求分析部分,在网关基础代理的服务中,网关作为…

生信豆芽菜-edgeR差异分析使用说明

网站&#xff1a;http://www.sxdyc.com/diffEdgerAnalyse 一、edgeR差异分析简介 edgeR使用经验贝叶斯估计和基于负二项模型的精确检验来确定差异基因&#xff0c;通过在基因之间来调节跨基因的过度离散程度&#xff0c;使用类似于Fisher精确检验但适应过度分散数据的精确检验用…

GateWay网关使用

流程如下&#xff1a; 1、微服务启动&#xff0c;将自己注册到Nacos&#xff0c;Nacos记录了各微服务实例的地址。 2、网关从Nacos读取服务列表&#xff0c;包括服务名称、服务地址等。 3、请求到达网关&#xff0c;网关将请求路由到具体的微服务。 1.导入依赖 <!--网关…

动手吧,vue移动端消息滚动组件

先看效果图&#xff1a; 1、模板部分 <transition name"fade-sport"><div class"v-message-roll" v-show"visible"><svg class"v-icon" viewBox"0 0 1024 1024" version"1.1" xmlns"http://…

Windows环境下通过 系统定时 执行脚本方式 压缩并备份文件夹 到其他数据盘

环境配置 压缩时需要使用7-zip进行调用&#xff0c;因此根据自己电脑进行安装 官网&#xff1a;https://www.7-zip.org/ 脚本文件 新建记事本文件&#xff0c;重命名为git_back_up.bat echo off rem 设置utf-8可以正常显示中文 chcp 65001 > nulrem 获取当前日期和时间&…

树和二叉树基础概念

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&…

Webpack 的 sass-loader 在生产模式下最小化 CSS 问题

学习webpack时候我发现一个问题&#xff1a; 将mode 改为production模式后&#xff0c;生成的css会被压缩了&#xff0c;但是我并没有引入CssMinimizerPlugin插件&#xff0c;然后我试着将optimization.minimize 设置为false&#xff0c;测试是否为webpack自带的压缩&#xff0…

微星笔记本:为京津冀、黑吉辽受灾用户提供一系列维修服务

面对近期集中在华北、东北等地出现的极端降水&#xff0c;引发洪涝和地质灾害。全国人民都众志成城、万众一心&#xff0c;在各个环节上贡献自己一份力量。台风无情、微星有爱&#xff0c;为保障您的电脑正常使用&#xff0c;京津冀、黑吉辽全区域微星线下服务中心及微星上门服…

day0808

1.单链表实现约瑟夫环 #include "joseph.h" LoopLink list_create(int m) {LoopLink L (LoopLink)malloc(sizeof(Node));if(NULLL){printf("内存创建失败\n");return 0;}LoopLink qL;for(int i1; i<m; i){LoopLink p (LoopLink)malloc(sizeof(Node));…

年轻代频繁GC ParNew导致http变慢

背景介绍 某日下午大约四点多&#xff0c;接到合作方消息&#xff0c;线上环境&#xff0c;我这边维护的某http服务突然大量超时&#xff08;对方超时时间设置为300ms&#xff09;&#xff0c;我迅速到鹰眼平台开启采样&#xff0c;发现该服务平均QPS到了120左右&#xff0c;平…

管理类联考——逻辑——论证逻辑——汇总篇——真题和典例——削弱

削弱 199-2014-10-41——割裂关系 卫计委的报告表明&#xff0c;这些年来医疗保健费的确是增加了。可见&#xff0c;我们每个人享受到的医疗条件大大改善了。 以下哪项对上述结论提出最严重的质疑? A.医疗保健费的绝大部分用在了对高危病人的高技术强化护理上。 B.在不增加费…

容器——4. Map 接口

文章目录 4.1. HashMap 和 Hashtable 的区别4.2. HashMap 和 HashSet 区别4.3. HashMap 和 TreeMap 区别4.4. HashSet 如何检查重复4.5. HashMap 的底层实现4.5.1. JDK1.8 之前4.5.2. JDK1.8 之后 4.6. HashMap 的长度为什么是 2 的幂次方4.7. HashMap 多线程操作导致死循环问题…

STL模型修复权威指南【3D打印】

设计师和工程师通常需要软件来调整、修复和最终确定 3D 打印的 3D 模型。 幸运的是&#xff0c;手动网格编程的时代早已一去不复返了。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 如今&#xff0c;有一系列专用工具可提供自动和手动 STL 修复功能。 自动向导足以满…

zju代码题:4-6

一 分段函数算水费 #include <stdio.h>int main() {/*** 定义两个* 定义浮点型变量* y:水费* x:用水的吨数* */double x, y;printf("Enter x(x>=0):\n"