当前位置: 首页 > news >正文

C++--二叉搜索树

目录

二叉搜索树的结构

insert插入

中序遍历

查找find

删除erase

key/value使用场景


二叉搜索树

什么是二叉搜索树?

左子树上所有节点的值都小于根,右子树上所有节点的值都大于根,它的左右子树也都是二叉搜索树

二叉搜索树的效率?

最好情况下,是一个完全二叉树,高度为log2 N;最差情况下,退化成一个单枝二叉树,高度为N

所以,二叉搜索树的增删查改效率为O(N)

二分查找也可以实现log2N的查找效率,但它有几个巨大的缺陷

  • 要求存储在能进行下标随机访问的结构中,且必须有序
  • 插入和删除的效率低,因为需要大量移动数据

二叉搜索树包括两个版本-冗余版本和非冗余版本

这里进行非冗余版本的介绍,即不允许存在值相等的节点,拒绝重复值

二叉搜索树的结构

template<class k>
struct BTNode
{BTNode(const k& key ):_key(key),_left(nullptr),_right(nullptr){}k _key;//关键字BTNode<k>* _left;BTNode<k>* _right;
};template<class k>
class BTree
{using Node = BTNode<k>;//C++11中的起别名,相当于typedef
private:Node* _root=nullptr;
};

insert插入

分两种情况

  1. 树为空,则创建一个值为key的新节点,将它赋给_root
  2. 树不为空,则要找到要插入节点的位置,插入值比当前值大的往左走,小的往右走

插入节点一定要和父亲链接上,所以要有一个parent指针

bool insert(const k& key)
{if (_root == nullptr){_root = new Node(key);return true;}//寻找插入位置Node* parent = _root;Node* cur = _root;while (cur != nullptr){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}elsereturn false;//相等则插入失败}//那我是插在parent的左边还是右边呢?if (key < parent->_key){parent->_left = new Node(key);}else// (key > parent->_key){parent->_right = new Node(key);}return true;
}

中序遍历

访问顺序:左子树->根->右子树,由于搜索二叉树的左>根>右,打印出来的一定是个严格递增的数列

利用递归

void _InOrder(Node* root)
{//递归的结束条件if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

由于private,不能在外面访问_root

如果在主函数里面传参,BTree tree1;_InOrder(tree1->_root),但是_root只能在类里面进行访问,在外面禁止访问,因此我们还要提供一个接口,InOrder。

void InOrder()
{_InOrder(_root);
}void _InOrder(Node* root)
{//递归的结束条件if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

这样传参时只用调用 InOrder(),在内部自动调用_InOrder()

查找find

直接从根节点_root的值开始比较,如果key的值比当前节点的值大,那就往左走,比当前节点的值小,那就往右走,看走到空之前是否找到

(对于冗余版本,如果找到了则返回中序第一次出现的x)

bool Find(const k& key)
{Node* cur = _root;while (cur != nullptr){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}elsereturn true;}return false;//节点遍历完了还没找到
}

删除erase

1.删除叶子节点,直接给父亲打声招呼

2.删除非叶子节点有两种情况

a)这个节点有一个孩子,那就跟爹打声招呼,把娃交给你

b)这个节点有两个孩子,

将这个节点删除,那这个节点的父亲就要管理三个孩子,但这是一颗二叉树,因此我们得找一个节点来替代这个删除的节点,并且不能破坏这个二叉搜索树的结构,

这个替换的节点要满足,替换后的节点的值要大于这个位置的左子树的值,要小于这个位置的右子树的值。

如何选择替换节点?

找这个待删除节点左子树的最大值(左子树最右侧的节点),因为这个待删除节点的左子树所有的值都<待删除节点的值<这个待删除节点的右子树的所有的值。那我们就找出左子树当中的最大值,来作为替换节点,它一定满足,小于待删除节点的右子树的所有的值,大于待删除节点的左子树所有的值。

或者,找这个待删除结点右子树的最小值(右子树最左侧的节点)。同理,这个替换节点一定满足,它是右子树当中最小的节点,是左子树当中最大的节点。

  • 删除的第一步是查找,就是上文的find操作
  • 删除之前需要给删除节点的父亲打声招呼,因此需要记录待删除节点的父亲
  • 也要记录替换节点的父亲,因为替换节点也会有孩子,那就要将爷孙链接
  • 而且要注意不要删除原节点,要删除替换节点,避免复杂的重新链接
bool Erase(const k& key)
{Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else//相等了 找到要删除的节点{//进行删除操作//首先我得确定这个待删除结点的孩子情况,是左孩子为空,还是右孩子为空,还是左右孩子都不为空if (cur->_left == nullptr)//删除节点的左孩子为空{if (cur == _root){_root = _root->_right;}//然后我得知道这个待删除的节点是父亲的左孩子还是右孩子,这样才能进行父亲和孙子的链接//前提,得有parentelse{if (parent->_left == cur)//带删除的节点是父亲的左孩子{//那父亲的左孩子就得指向待删除结点的右孩子parent->_left = cur->_right;}else//待删除节点是父亲的右孩子{//将父亲的右孩子指向待删除结点的右孩子parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//删除节点的右孩子为空{if (cur == _root){_root = _root->_left;}//判断删除节点是父节点的左还是右//前提,得有parentelse{if (parent->_left == cur)//带删除的节点是父亲的左孩子{//将父亲的左孩子指向待删除结点的左孩子parent->_left = cur->_left;}else//待删除的节点是父亲的右孩子{//将父亲的右孩子指向待删除结点的右孩子parent->_right = cur->_left;}}delete cur;}else//删除节点有两个孩子,{//接下来进行替换//找这个待删除结点左子树中的最大值,即最右侧的节点Node* replace = cur->_left;//记录替换节点的父亲,确保能将替换节点的孩子不丢失Node* replace_parent = cur;while (replace->_right != nullptr){replace_parent = replace;replace = replace->_right;}//replace现在为左子树最右侧的节点//将待删除结点的key替换成replace的keycur->_key = replace->_key;//接下来就要确定替换节点是父亲的左孩子还是右孩子if (replace_parent->_left == replace)//替换节点是其父亲的左孩子{replace_parent->_left = replace->_left;//将替换节点的父节点与替换节点的左孩子相连}else{replace_parent->_right = replace->_left;}//删除替换节点,而非原节点delete replace;}return true;}}return false;
}

二叉搜索树的删除算法面试常考

搜索二叉树不允许修改key,因为已有可能破坏这棵树的结构

key/value使用场景

比如通讯录,通过key找到“张三”,通过key找到value,value是电话号码,即key是索引关键字,value是内容,可以是任意类型

key是唯一标识,不支持修改,但value可以修改

再比如key是车牌号,value是车进入停车场的起始时间

在key的基础上改动:

  • 在节点BTNode中新增一个成员变量value
  • 在插入insert逻辑中,new(key,value)
  • find的返回值改为BTNode<k,v>*
  • erase中增加cur->value=replace->valus
  • 中序遍历增加cur->value的打印
template<class k,class v>
struct BTNode
{BTNode(const k& key,const v& value):_key(key),_value(value), _left(nullptr), _right(nullptr){}k _key;//关键字v _value;BTNode<k, v>* _left;BTNode<k, v>* _right;
};
template<class k,class v>
class BTree
{using Node = BTNode<k, v>;//C++11中的起别名,相当于typedef
public:bool insert(const k& key,const v& value){if (_root == nullptr){_root = new Node(key, value);return true;}//寻找插入位置Node* parent = _root;Node* cur = _root;while (cur != nullptr){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}elsereturn false;}//那我是插在parent的左边还是右边呢?if (key < parent->_key){parent->_left = new Node(key, value);}else {parent->_right = new Node(key, value);}return true;}BTNode<k,v>* Find(const k& key){Node* cur = _root;while (cur != nullptr){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}elsereturn cur;}return nullptr;//节点遍历完了还没找到}void InOrder(){_InOrder(_root);}void _InOrder(Node* root){//递归的结束条件if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " " << root->_value;_InOrder(root->_right);}bool Erase(const k& key){Node* parent = nullptr;Node* cur = _root;while (cur != nullptr){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else//相等了 找到要删除的节点{//进行删除操作//首先我得确定这个待删除结点的孩子情况,是左孩子为空,还是右孩子为空,还是左右孩子都不为空if (cur->_left == nullptr)//删除节点的左孩子为空{if (cur == _root){_root = _root->_right;}//然后我得知道这个待删除的节点是父亲的左孩子还是右孩子,这样才能进行父亲和孙子的链接//前提,得有parentelse{if (parent->_left == cur)//带删除的节点是父亲的左孩子{//那父亲的左孩子就得指向待删除结点的右孩子parent->_left = cur->_right;}else//待删除节点是父亲的右孩子{//将父亲的右孩子指向待删除结点的右孩子parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//删除节点的右孩子为空{if (cur == _root){_root = _root->_left;}//判断删除节点是父节点的左还是右//前提,得有parentelse{if (parent->_left == cur)//带删除的节点是父亲的左孩子{//将父亲的左孩子指向待删除结点的左孩子parent->_left = cur->_left;}else//待删除的节点是父亲的右孩子{//将父亲的右孩子指向待删除结点的右孩子parent->_right = cur->_left;}}delete cur;}else//删除节点有两个孩子,{//接下来进行替换//找这个待删除结点左子树中的最大值,即最右侧的节点Node* replace = cur->_left;//记录替换节点的父亲,确保能将替换节点的孩子不丢失Node* replace_parent = cur;while (replace->_right != nullptr){replace_parent = replace;replace = replace->_right;}//replace现在为左子树最右侧的节点//将待删除结点的key替换成replace的keycur->_key = replace->_key;cur->_value = replace->_value;//接下来就要确定替换节点是父亲的左孩子还是右孩子if (replace_parent->_left == replace)//替换节点是其父亲的左孩子{replace_parent->_left = replace->_left;//将替换节点的父节点与替换节点的左孩子相连}else{replace_parent->_right = replace->_left;}//删除替换节点,而非原节点delete replace;}return true;}}return false;}private:Node* _root = nullptr;
};
http://www.dtcms.com/a/449426.html

相关文章:

  • dw网站怎么做点击图片放大潍坊建筑公司排名
  • 短视频素材网站免费大推荐淘宝seo 优化软件
  • 超声波图像乳腺癌识别分割数据集647张2类别
  • 【基于MQ的多任务分发体系】
  • DeploySharp开源发布:让C#部署深度学习模型更加简单
  • 网站开发专业就业指导可视化网站制作软件
  • 【MySQL】 SQL图形化界面工具DataGrip
  • PostgreSQL 安装与操作指南
  • iis怎么做网站cho菌主题wordpress
  • 网站大全软件下载淘宝搜索框去什么网站做
  • Python 编程语言介绍
  • 以太网接口
  • OpenHarmony(开源鸿蒙)小白入门教程
  • 光通信|OAM-偏振联合交叉连接
  • 广州建设工程质量安全网站网站数据库怎么备份
  • 【性能优化】帧率优化方法:第一步——量化
  • 【Docker项目实战】使用Docker部署ShowDoc文档管理工具
  • 第13课:成本与性能优化:语义缓存(Semantic Cache)实战
  • 网站搭建备案吗柳州网站seo
  • Witsy: 桌面 AI 助手 / 通用 MCP 客户端
  • 哈尔滨营销网站建设公司哪家好做视频分享网站的参考书
  • 音频焦点学习之AudioFocusRequest.Builder类剖析
  • 国产某能谱仪产品分析
  • 《Vuejs设计与实现》第 5 章(非原始值响应式方案)下 代理数组
  • 网站服务器速度查询北京网站设计公司兴田德润放心
  • 版本控制器git(1)--- git 初识与安装
  • 网站如何收录网络营销策划方案ppt
  • Three.js NodeMaterial 节点材质系统文档
  • 2025 中小企业 AI 转型:核心岗技能 “怎么证、怎么用”?
  • ML4T - 第8章第1节 蒙特卡洛估计夏普率 Monte Carlo Estimation of Sharpe Ratio