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

【C++融会贯通】二叉树进阶

目录

一、内容说明

二、二叉搜索树

2.1 二叉搜索树概念

2.2 二叉搜索树操作

2.2.1 二叉搜索树的查找 

2.2.2 二叉搜索树的插入

2.2.3 二叉搜索树的删除

2.3 二叉搜索树的实现

2.3.1 二叉搜索树的节点设置

2.3.2 二叉搜索树的查找函数

2.3.2.1 非递归实现

2.3.2.2 递归实现

2.3.3 二叉搜索树的插入函数

2.3.3.1 非递归实现

2.3.4 二叉搜索树的中序遍历

2.3.5 二叉搜索树的删除函数

2.3.5.1 非递归实现当我们需要删除一个节点时找到删除节点,需要记录当前节点的父亲节点,防止删除的当前节点时,防止当前的节点的父亲节点中的指针变为野指针

2.3.6 二叉搜索树代码的整体实现

2.4 二叉搜索树的应用

2.5 二叉搜索树的性能分析

结尾


一、内容说明

二叉树在前面C数据结构阶段已经讲过:非线性表之堆的实际应用和二叉树的遍历-CSDN博客

  1. mapset特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构
  2. 二叉搜索树的特性了解,有助于更好的理解mapset的特性
  3. 二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘
  4. 有些OJ题使用C语言方式实现比较麻烦,比如有些地方要返回动态开辟的二维数组,非常麻烦。 

此次二叉树是为后面mapset的实现做铺垫,也是对二叉树进行收尾。


二、二叉搜索树

2.1 二叉搜索树概念

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

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


 2.2 二叉搜索树操作

2.2.1 二叉搜索树的查找 
  • a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
  • b、最多查找高度次,走到到空,还没找到,这个值不存在。
2.2.2 二叉搜索树的插入

插入的具体过程如下:

  • a. 树为空,则直接新增节点,赋值给root指针
  • b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

2.2.3 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况:

  • a. 要删除的结点无孩子结点
  • b. 要删除的结点只有左孩子结点
  • c. 要删除的结点只有右孩子结点
  • d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程 如下:

  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

 2.3 二叉搜索树的实现

2.3.1 二叉搜索树的节点设置
template<class K>
struct BSTreeNode
{K _val;BSTreeNode<K>* _left;BSTreeNode<K>* _right;BSTreeNode(const K& val):_val(val),_left(nullptr),_right(nullptr){}
};

2.3.2 二叉搜索树的查找函数
  1. 从根节点开始查找,若查找值比根节点小,继续向左子树寻找,若查找值比根节点大,继续向右子树寻找。
  2. 最多查找高度次,若查找到空,那么在这棵树中没有这个值,返回false。 
2.3.2.1 非递归实现
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_val > key){cur = cur->_left;}else if(cur->_val < key){cur = cur->_right;}else{return true;}}return false;}
private:// 成员变量Node* _root = nullptr;
};
2.3.2.2 递归实现
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:bool FindR(const K& key)//使用递归查找{return _FindR(_root, key);}
private:bool _FindR(Node* _root,const K& key){if (_root == nullptr)return false;if (_root->_val > key){return _FindR(_root->_left, key);}else if (_root->_val < key){return _FindR(_root->_right, key);}else{return true;}}
private:Node* _root;
};

2.3.3 二叉搜索树的插入函数
  1. 若树为空,创建一个节点,赋值给root。
  2. 若树不为空,按照二叉搜索树的性质查找插入位置,插入新节点
2.3.3.1 非递归实现
//插入
bool Insert(const K& key)
{Node* cur = _root;Node* parent = nullptr;if (_root == nullptr){Node* newnode = new Node(key);_root = newnode;}else{while (cur){if (cur->_val > key){parent = cur;cur = cur->_left;}else if (cur->_val < key){parent = cur;cur = cur->_right;}else{return false;}}Node* newnode = new Node(key);if (parent->_val > key){parent->_left = newnode;}else{parent->_right = newnode;}}return true;
}

2.3.3.2 递归实现

bool InsertR(const K& key)//使用递归插入
{return _InsertR(_root, key);
}bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_val < key){return _InsertR(root->_right, key);}else if (root->_val > key){return _InsertR(root->_left, key);}else{return false;}
}

2.3.4 二叉搜索树的中序遍历

二叉树的中序遍历是:先递归遍历左子树,再访问根节点,最后递归遍历右子树。

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* cur)
{	//递归的形式遍历if (cur == nullptr)return;_InOrder(cur->_left);cout << cur->_val << " ";_InOrder(cur->_right);
}

2.3.5 二叉搜索树的删除函数
2.3.5.1 非递归实现
当我们需要删除一个节点时找到删除节点,需要记录当前节点的父亲节点,防止删除的当前节点时,防止当前的节点的父亲节点中的指针变为野指针
bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_val > key){parent = cur;cur = cur->_left;}else if (cur->_val < key){parent = cur;cur = cur->_right;}else//找到了需要找的值{if (cur->_left == nullptr)//左为空{if (parent == nullptr)//删除结点为根结点{_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}//右为空else if (cur->_right == nullptr){if (parent == nullptr)//删除结点为根结点{_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}//左右不为空else{Node* parent = cur;Node* leftmax = cur->_left;while (leftmax->_right){parent = leftmax;leftmax = leftmax->_right;}swap(cur->_val, leftmax->_val);if (parent->_left == leftmax){parent->_left = leftmax->_left;}else{parent->_right = leftmax->_left;}cur = leftmax;}delete cur;return true;}}return false;
}

2.3.6.2 递归实现
这里bool _EraseR(Node*& root, const K& key)&的作用也是通过改变当前节点直接改变父亲节点的指向。

bool EraseR(const K& key)//使用递归删除
{return _EraseR(_root, key);
}
bool _EraseR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_val > key){_EraseR(root->_left, key);}else if (root->_val < key){_EraseR(root->_right, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}
}

2.3.6 二叉搜索树代码的整体实现
namespace lxp
{template<class K>struct BSTreeNode{K _val;BSTreeNode<K>* _left;BSTreeNode<K>* _right;BSTreeNode(const K& val):_val(val),_left(nullptr),_right(nullptr){}};template<class K>class BSTree{typedef BSTreeNode<K> Node;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(){Destroy(_root);}//插入bool Insert(const K& key){Node* cur = _root;Node* parent = nullptr;if (_root == nullptr){Node* newnode = new Node(key);_root = newnode;}else{while (cur){if (cur->_val > key){parent = cur;cur = cur->_left;}else if (cur->_val < key){parent = cur;cur = cur->_right;}else{return false;}}Node* newnode = new Node(key);if (parent->_val > key){parent->_left = newnode;}else{parent->_right = newnode;}}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_val > key){cur = cur->_left;}else if(cur->_val < key){cur = cur->_right;}else{return true;}}return false;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_val > key){parent = cur;cur = cur->_left;}else if (cur->_val < key){parent = cur;cur = cur->_right;}else//找到了需要找的值{if (cur->_left == nullptr)//左为空{if (parent == nullptr)//删除结点为根结点{_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}//右为空else if (cur->_right == nullptr){if (parent == nullptr)//删除结点为根结点{_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}//左右不为空else{Node* parent = cur;Node* leftmax = cur->_left;while (leftmax->_right){parent = leftmax;leftmax = leftmax->_right;}swap(cur->_val, leftmax->_val);if (parent->_left == leftmax){parent->_left = leftmax->_left;}else{parent->_right = leftmax->_left;}cur = leftmax;}delete cur;return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* cur){	//递归的形式遍历if (cur == nullptr)return;_InOrder(cur->_left);cout << cur->_val << " ";_InOrder(cur->_right);}bool FindR(const K& key)//使用递归查找{return _FindR(_root, key);}bool InsertR(const K& key)//使用递归插入{return _InsertR(_root, key);}bool EraseR(const K& key)//使用递归删除{return _EraseR(_root, key);}private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_val);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool _EraseR(Node* root, const K& key){if (root == nullptr)return false;if (root->_val > key){_EraseR(root->_left, key);}else if (root->_val < key){_EraseR(root->_right, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_val < key){return _InsertR(root->_right, key);}else if (root->_val > key){return _InsertR(root->_left, key);}else{return false;}}bool _FindR(Node* _root,const K& key){if (_root == nullptr)return false;if (_root->_val > key){return _FindR(_root->_left, key);}else if (_root->_val < key){return _FindR(_root->_right, key);}else{return true;}}private:Node* _root;};
}


2.4 二叉搜索树的应用

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

        比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:   

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2. KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对。
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):_key(key), _value(value), _left(nullptr), _right(nullptr){}
};template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:	bool Insert(const K& key, const V& value){// 树为空则,该节点为根if (_root == nullptr){_root = new Node(key, value);return true;}Node* cur = _root;Node* prev = nullptr;while (cur){prev = cur;// 当当前节点的值大于要插入的值,那么向左找if (cur->_key > key){cur = cur->_left;}// 当当前节点的值小于要插入的值,那么向右找else if (cur->_key < key){cur = cur->_right;}// 二叉搜索树不允许相同的值存在,当这个值已经存在过了,那么返回false,即插入失败else{return false;}}if (prev->_key > key){prev->_left = new Node(key, value);return true;}else{prev->_right = new Node(key, value);return true;}return false;}	Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}// 找得到else{return cur;}}// 找不到return nullptr;}// 删除,非递归版本bool Erase(const K& key){Node* cur = _root;Node* prev = nullptr;// 找到需要删除的节点while (cur){if (cur->_key > key){prev = cur;cur = cur->_left;}else if (cur->_key < key){prev = cur;cur = cur->_right;}else{break;}}// 当当前根节点的左树为空时,那么链接右边if (cur->_left == nullptr){if (cur == _root){_root = cur->_left;delete cur;return true;}else{if (prev->_left == cur)prev->_left = cur->_right;elseprev->_right = cur->_right;delete cur;cur = nullptr;return true;}}// 当当前根节点的右树为空时,那么链接左边else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;delete cur;return true;}else{if (prev->_left == cur)prev->_left = cur->_left;elseprev->_right = cur->_left;delete cur;cur = nullptr;return true;}}else{prev = cur;Node* del = cur->_right;while (del->_left != nullptr){prev = del;del = del->_left;}swap(del->_key, cur->_key);if (prev->_left == del)prev->_left = del->_right;elseprev->_right = del->_right;delete del;del = nullptr;return true;}return false;}void InOrder(){_InOrder(_root);cout << endl;}~BSTree(){Destory(_root);}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;}private:Node* Copy(Node* Troot){if (Troot == nullptr)return nullptr;Node* root = new Node(Troot->_key);root->_left = Copy(Troot->_left);root->_right = Copy(Troot->_right);return root;}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key > key)return _InsertR(root->_left, key);else if (root->_key < key)return _InsertR(root->_right, key);// 出现相同的数字,返回falseelsereturn false;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ':' << root->_value << endl;_InOrder(root->_right);}Node* _root = nullptr;
};void TestBSTree()
{BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("left", "左边");dict.Insert("string", "字符串");dict.InOrder();string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_value << endl;}else{cout << "单词拼写错误" << endl;}}string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}

2.5 二叉搜索树的性能分析

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN(log以2为底N的对数)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上场了。 


结尾

如果有什么建议和疑问,或是有什么错误,希望大家可以在评论区提一下。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,请大家给一个三连支持一下!!

谢谢大家收看🌹🌹

http://www.lryc.cn/news/484346.html

相关文章:

  • 使用python-Spark使用的场景案例具体代码分析
  • 如何查看本地的个人SSH密钥
  • 本人认为 写程序的三大基本原则
  • A030-基于Spring boot的公司资产网站设计与实现
  • React Hooks 深度解析与实战
  • #渗透测试#SRC漏洞挖掘#蓝队基础之网络七层杀伤链04 终章
  • 计算机毕业设计Python+大模型农产品推荐系统 农产品爬虫 农产品商城 农产品大数据 农产品数据分析可视化 PySpark Hadoop
  • 爬虫补环境案例---问财网(rpc,jsdom,代理,selenium)
  • SpringBoot有几种获取Request对象的方法
  • 在 Windows 11 中使用 MuMu 模拟器 12 国际版配置代理
  • ASP.NET Core Webapi 返回数据的三种方式
  • SQL面试题——蚂蚁SQL面试题 连续3天减少碳排放量不低于100的用户
  • Python酷库之旅-第三方库Pandas(216)
  • 论文解析:计算能力资源的可信共享:利益驱动的异构网络服务提供机制
  • Spring AOP技术
  • 数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目)
  • MATLAB保存多帧图形为视频格式
  • redis7.x源码分析:(3) dict字典
  • 连续九届EI稳定|江苏科技大学主办
  • HarmonyOS NEXT应用开发实战 ( 应用的签名、打包上架,各种证书详解)
  • 【CICD】CICD 持续集成与持续交付在测试中的应用
  • Dolby TrueHD和Dolby Digital Plus (E-AC-3)编码介绍
  • 数字频率计的设计-- 基于 HDL 方法
  • [程序员] 没有产生core文件的原因
  • 【数字图像处理+MATLAB】基于 Sobel 算子计算图像梯度并进行边缘增强:使用 imgradientxy 函数
  • P10901 [蓝桥杯 2024 省 C] 封闭图形个数
  • ubuntu-desktop-24.04上手指南(更新阿里源、安装ssh、安装chrome、设置固定IP、安装搜狗输入法)
  • 手机直连卫星NTN通信初步研究
  • 蓝桥杯c++算法学习【2】之搜索与查找(九宫格、穿越雷区、迷宫与陷阱、扫地机器人:::非常典型的必刷例题!!!)
  • Android加载pdf