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

【C++】二叉搜索树

在这里插入图片描述

文章目录

  • 一、概念与性能分析
  • 二、二叉搜索树的实现
    • 1. 结点
    • 2. 默认成员函数
    • 3. 插入
    • 4. 查找
    • 5. 删除
    • 6. 中序遍历
    • 7. 完整实现
  • 三、key和key/value

一、概念与性能分析

二叉搜索树,又称搜索二叉树、二叉排序树,是一种具有特殊性质的二叉树:

  • 左子树上所有结点的值都小于等于根结点的值
  • 右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也都是二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看场景需求

由于它本身和它的子树都是二叉搜索树,不难想象:二叉搜索树的中序遍历一定是升序序列

从名字就能看出来,二叉搜索树主要用于搜索。最优情况下,如果它接近完全二叉树的结构,不难分析,最大搜索次数即其树的高度:log2N;最差情况下,如果它接近单支树的结构,则其搜索次数接近结点个数:N。在这里插入图片描述
所以综合来看二叉搜索树的增删查改效率为O(N),这样的时间复杂度还是太高了。必须将二叉搜索树的结构进一步优化,也就是以后要学习的平衡二叉搜索树AVL树、红黑树。

二、二叉搜索树的实现

1. 结点

首先需要定义出结点的结构:

template<class K>
struct BSTreeNode
{BSTreeNode* _left;BSTreeNode* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){ }
};

2. 默认成员函数

由于树的类中涉及到结点资源的开辟和删除,所以需要我们自己写拷贝构造、赋值重载、析构函数等。

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
private:Node* _root;public:
//......
}
  • 默认构造函数:
//默认构造一棵空树
BSTree():_root(nullptr)
{ }

之前我们学习链式结构的树,它的很多操作都需要用递归完成,但我们这里的拷贝构造、析构函数没办法传参递归怎么办?我们可以写出递归的函数后,“包装”起来:

  • 拷贝构造函数:
BSTree(const BSTree<K>& t)
{//实际完成拷贝功能的是Copy函数,我们的拷贝构造只是把他包装起来_root = Copy(t._root);
}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;
}
  • 析构函数:
~BSTree()
{Destory(_root);_root = nullptr;
}void Destory(Node* root)
{if (root == nullptr){return;}//利用递归完成所有结点的资源释放Destory(root->_left);Destory(root->_right);delete root;
}

拷贝构造函数的Copy函数和析构函数的Destory函数仅供它们内部使用,所以可以把它们放到private部分

  • 赋值运算符重载:
BSTree<K>& operator=(BSTree<K> t)
{swap(_root, t._root);return *this;
}

3. 插入

二叉搜索树的插入要按照搜索树的性质寻找正确位置插入:

  • 树为空,则直接新增结点作为root
  • 树不为空,则按照二叉搜索树性质,插入值比当前节点值大就往右走,插入值比当前节点值小则往左走。直到找到空位置,插入新节点:

在这里插入图片描述
代码实现:

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}//parent保存待插入位置的父结点Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//如果key值已存在,则插入失败return false;}}//cur找到了正确的位置cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

4. 查找

查找一个值是否存在,也很简单了:

bool Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else //key == cur->_key,找到了{return true;}}return false;
}

5. 删除

二叉搜索树的删除相对复杂,首先查找待删除元素结点,不存在则返回false。
若存在,则显然有以下几种情形:(假设要删除的结点为x)
x没有孩子、有左孩子或右孩子、有两个孩子。
为了删除结点后不破坏搜索二叉树的性质,它们要用不同的解决方法:

  • 若x没有孩子,则直接删除x
  • 若x只有一个孩子,则让x的父亲的原指向x的指针改为指向x的孩子,再删除x
  • 若x有两个孩子,使用替换法:找x的左子树的最大结点l(即左子树的最右结点),或x的右子树的最小结点r(即右子树的最左结点),代替x。因为这两个结点的值替换x后,都还能保持搜索二叉树的性质。最后再删除l或r结点,因为l或r结点一定是没有孩子或只有一个孩子的,再用上面两种情形方法处理就好。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现,可以将情形1和情形2整合讨论:

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else //找到了待删除结点cur,开始删除{//如果cur左孩子为空,就连接parent和cur的右子if (cur->_left == nullptr){//特殊情况cur为根结点,cur右孩子当根结点if (cur == _root){_root = cur->_right;}else{//如果cur是parent的左,则让parent的左指向cur的右子if (cur == parent->_left){parent->_left = cur->_right;}//如果cur是parent的右,则让parent的右指向cur的右子else{parent->_right = cur->_right;}}delete cur;}//如果cur右孩子为空,就连接parent和cur的左子else if (cur->_right == nullptr){//特殊情况cur为根结点,cur左孩子当根结点if (cur == _root){_root = cur->_left;}else{//如果cur是parent的左,则让parent的左指向cur的左子if (cur == parent->_left){parent->_left = cur->_left;}//如果cur是parent的右,则让parent的右指向cur的左子else{parent->_right = cur->_left;}}delete cur;}else //两个孩子,这里选择找左子树的最大结点替换{Node* LeftMaxParent = cur;Node* LeftMax = cur->_left;//找左子树的最右结点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}swap(cur->_key, LeftMax->_key);//此时LeftMax一定没有右孩子,可能有左孩子//还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             if (LeftMaxParent->_left == LeftMax){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}return true;}}return false;
}

6. 中序遍历

为了验证二叉搜索树的性质,我们要判断中序遍历的结果是否是升序的:

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

同样地,将_InOrder函数放在private中。

7. 完整实现

综上,二叉搜索树的完整实现:

namespace lydly
{template<class K>struct BSTreeNode{BSTreeNode* _left;BSTreeNode* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){ }};template<class K>class BSTree{typedef BSTreeNode<K> Node;private:Node* _root;public://默认构造一棵空树BSTree():_root(nullptr){ }//拷贝构造BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_root);_root = nullptr;}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{//如果key值已存在,则插入失败return false;}}//cur找到了正确的位置cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else //key == cur->_key找到了{return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else //找到了待删除结点,开始删除{//如果cur左孩子为空,就连接parent和cur的右子if (cur->_left == nullptr){//特殊情况cur为根结点,cur右子当根if (cur == _root){_root = cur->_right;}else{//如果cur是parent的左,则让parent的左指向cur的右子if (cur == parent->_left){parent->_left = cur->_right;}//如果cur是parent的右,则让parent的右指向cur的右子else{parent->_right = cur->_right;}}delete cur;}//如果cur右孩子为空,就连接parent和cur的左子else if (cur->_right == nullptr){//特殊情况cur为根结点,cur左子当根if (cur == _root){_root = cur->_left;}else{//如果cur是parent的左,则让parent的左指向cur的左子if (cur == parent->_left){parent->_left = cur->_left;}//如果cur是parent的右,则让parent的右指向cur的左子else{parent->_right = cur->_left;}}delete cur;}else //两个孩子,这里选择找左子树的最大结点替换{Node* LeftMaxParent = cur;Node* LeftMax = cur->_left;//找左子树的最右结点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}swap(cur->_key, LeftMax->_key);//此时LeftMax一定没有右孩子,可能有左孩子//还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             if (LeftMaxParent->_left == LeftMax){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void Destory(Node* root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete root;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}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;}};
} 

简单测试:
在这里插入图片描述
没有问题。

三、key和key/value

上面我们实现的二叉搜索树,结点中只存储了一个key,称为关键码,关键码即为需要搜索的值,使用这种二叉搜索树的场景只需要判断key是否存在。key的搜索场景实现的二叉搜索树支持增删查,不支持修改,因为修改key值破坏二叉搜索树性质了。

使用这种key二叉搜索树的场景,比如小区车库,只有业主的车能进入,车牌号作为key值,就在系统中检查key是否存在以确定是否为业主的车。

还有一种key/value的搜索场景:每一个关键码key,都有与之对应的value,value可以为任意类型对象,增删查还是以key为关键码在二叉搜索树中操作,修改不能改key,但是可以修改value。

使用这种key/value二叉搜索树的场景,比如英语词典,树的结构中存储key(英文)和value(中文),搜索时输入英文作为key进行查找,找到后输出对应的value中文。

key/value二叉搜索树的实现:

namespace lydly
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;   V _value; BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;private:Node* _root = nullptr;public:BSTree(){ }BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destory(_root);_root = nullptr;}bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}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->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(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->_left;}else{// 准备删除if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else // 两个孩子{Node* pMinRight = cur;Node* minRight = cur->_right;while (minRight->_left){pMinRight = minRight;minRight = minRight->_left;}swap(cur->_key, minRight->_key);if (pMinRight->_left == minRight){pMinRight->_left = minRight->_right;}else{pMinRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}void Destory(Node* root){if (root == nullptr){return;}Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copy = new Node(root->_key, root->_value);copy->_left = Copy(root->_left);copy->_right = Copy(root->_right);return copy;}};}

本篇完,感谢阅读。

http://www.lqws.cn/news/143857.html

相关文章:

  • ocrapi服务docker镜像使用
  • 从零开始的云计算——番外实战,iptables防火墙项目
  • WordZero:让Markdown与Word文档自由转换的Golang利器
  • 【Go语言基础【2】】数据类型之基础数据类型:数字、字符、布尔、枚举、自定义
  • 1、Go语言基础中的基础
  • 【Go语言基础【四】】局部变量、全局变量、形式参数
  • PPT转图片拼贴工具 v3.0
  • Spark 写文件
  • Dubbo Logback 远程调用携带traceid
  • 41道Django高频题整理(附答案背诵版)
  • PostgreSQL 的扩展pg_prewarm
  • 20250605在微星X99主板中配置WIN10和ubuntu22.04.6双系统启动的引导设置
  • Django CMS 的 Demo
  • NoSQL之Redis配置与优化
  • SQL Server相关的sql语句
  • 嵌入式学习 D33:系统编程--网路编程
  • ubuntu 端口复用
  • Ubuntu20.04设置为开机后直接自动进入纯命令行界面
  • 【Linux】为 Git 设置 Commit 提交模板方法,可统一个人或者项目的提交风格
  • 【Git系列】如何同步原始仓库的更新到你的fork仓库?
  • Excel-vlookup -多条件匹配,返回指定列处的值
  • [测试_10] Selenium IDE | cssSelector | XPath | 操作测试
  • Haproxy的基础配置
  • DeepSeek 助力 Vue3 开发:打造丝滑的日历(Calendar),日历_天气预报日历示例(CalendarView01_18)
  • 111页可编辑精品PPT | 华为业务变革框架及战略级项目管理华为变革管理华为企业变革华为的管理模式案例培训
  • EXCEL通过DAX Studio获取端口号连接PowerBI
  • 联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率
  • 三甲医院“AI平台+专家系统”双轮驱动模式的最新编程方向分析
  • 【个人笔记】数据库原理(西电)
  • vscode里如何用git