C++二叉树进阶

本期内容我们讲解二叉树的进阶知识,没有看过之前内容的小伙伴建议先看往期内容

二叉树-----补充_KLZUQ的博客-CSDN博客

目录

二叉搜索树 

代码实现

基础框架

Insert

Find

Erase

析构函数

拷贝构造

赋值

二叉搜索树的应用

全部代码


二叉搜索树 

二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树 :
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

 由于结构特性,二叉搜索树很适合查找数据,那它最多查找多少次呢?高度次吗?

其实不是的,二叉搜索树是可能会有右边这种情况的,时间复杂度是O(N)

 这其实是不好的,后面我们会讲AVL树和红黑树,他们的时间复杂度是O(longN)

话不多说,下面我们来实现二叉搜索树

代码实现

基础框架

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr),_key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){}bool Insert(const K& key){}
private:Node* _root;
};

我们先写出基本框架,接着我们来实现插入

Insert

假设我们有这样一棵树,我们要插入11,13和16,该怎么办呢?我们将11和root节点对比,11比8大,应该在右边,继续对比11和10,比10大,在右边,11比14小,在左边,比13小,在左边

就成了这个样子,再看13,我们对比后发现树里面已经有13了,所以插入失败,最后看16,16比14大,链接在14的右边即可

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 = 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;}

我们先用循环来实现

下面为了测试,我们写一个中序遍历

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

我们写成子函数是因为我们在调用时还得传root,比如封装写成子函数,这样我们就不用传了,下面进行测试

 没有问题,中序遍历出来是有序的

bool InsertR(const K& key){return _InsertR(_root, key);}
private:bool _InsertR(Node*& root, const K& key){if (root == nullptr)//因为引用,所以下面可以直接new{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;}}

我们再看递归版本的,递归版本里参数加了引用,这个引用可以让我们在插入节点时不用找该节点的父亲节点,可谓是神之一手 

Find

bool 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 true;}}return false;}

find也非常简单

bool FindR(const K& key){return _FindR(_root, key);}
private:bool _FindR(Node* root,const K& key)//递归版本{if (root == nullptr){return false;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}

还有递归版本的,我们再把子函数写成私有的 

Erase

删除是一个重点,我们有这样一棵树,假设我们要删除7,14和3

7是叶子节点,删除后再把6的右置为空即可,14也还好说,它只有一个孩子,我们可以让10的右节点指向13,最麻烦的是3,我们该如何删除3呢?这里就要用到替换法,用3的左子树的最大节点或者右子树的最小节点来替换,一棵树的最大节点是这棵树的最右边的节点,所以左子树的最大节点很好找,最小的节点就是最左边的,所以右子树的最小节点也很好找

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 (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}		}//右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else {if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}			}//左右都不为空else{//找替代节点Node* parent = cur;//这里不能给空,防止leftMax是根节点的左子树的第一个节点Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_left;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}

删除要考虑的情况非常多,大家一定要画图才能理解

 大家用这几张图画一画,结合代码,里面的坑是非常多的 

下面我们测试一下

没有问题,大家测试时再把整棵树删空测试一下

bool EraseR(const K& key){return _EraseR(_root, key);}
private: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->_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;}}

我们再看递归版本,这里同样使用了引用,可以方便很多,大家画图对照一下就可以理解

下面我们再完成一下析构函数

析构函数

~BSTree(){Destroy(_root);}
void Destroy(Node*& root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}

也是同样使用引用,为了方便置空

拷贝构造

BSTree(const BSTree<K>& t){_root = Copy(t._root);}
Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;}

拷贝构造这里是不能调用insert的,因为任何顺序的插入都不能保证和原来树的一样,大家可以试一试,而我们这样写拷贝构造就可以解决问题

赋值

BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}

赋值的话我们直接用现代写法即可

二叉搜索树的应用

1. K 模型: K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到 的值
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
以词库中所有单词集合中的每个单词作为 key ,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV 模型:每一个关键码 key ,都有与之对应的值 Value ,即 <Key, Value> 的键值对 。该种方式在现实生活中非常常见:
比如 英汉词典就是英文与中文的对应关系 ,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文 <word, chinese> 就构成一种键值对;
再比如 统计单词次数 ,统计成功后,给定单词就可快速找到其出现的次数, 单词与其出
现次数就是 <word, count> 就构成一种键值对

 下面我们修改我们上面的程序,把它变成KV的

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;public:BSTree():_root(nullptr){}void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key,value);}bool EraseR(const K& key){return _EraseR(_root, key);}private: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->_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,const V& value){if (root == nullptr)//因为引用,所以下面可以直接new{root = new Node(key,value);return true;}if (root->_key < key){return _InsertR(root->_right, key,value);}else if (root->_key > key){return _InsertR(root->_left, key,value);}else{return false;}}Node* _FindR(Node* root, const K& key)//递归版本{if (root == nullptr){return nullptr;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << ":"<<root->_value<<endl;_InOrder(root->_right);}private:Node* _root;};

这里我删除了一些,下面我们来进行测试

这里我们模拟一个字典 

还有统计水果出现的次数,没有出现的水果我们存进去,出现的让value++

全部代码

namespace key
{template<class K>struct BSTreeNode{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};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){if (_root == nullptr){_root = new Node(key);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);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool 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 true;}}return false;}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 (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}//右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}//左右都不为空else{//找替代节点Node* parent = cur;//这里不能给空,防止leftMax是根节点的左子树的第一个节点Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_left;}swap(cur->_key, leftMax->_key);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;}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->_key);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->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, 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)//因为引用,所以下面可以直接new{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 _FindR(Node* root, const K& key)//递归版本{if (root == nullptr){return false;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root;};void Test(){int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : a){t.InsertR(e);}t.InOrder();t.EraseR(4);t.InOrder();t.EraseR(6);t.InOrder();t.EraseR(7);t.InOrder();t.EraseR(3);t.InOrder();for (auto e : a){t.Erase(e);}t.InOrder();}void Test2(){int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : a){t.Insert(e);}t.InOrder();BSTree<int> t1(t);t1.InOrder();}
}namespace 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):_left(nullptr), _right(nullptr), _key(key),_value(value){} };template<class K,class V>class BSTree{typedef BSTreeNode<K,V> Node;public:BSTree():_root(nullptr){}void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key,value);}bool EraseR(const K& key){return _EraseR(_root, key);}private: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->_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,const V& value){if (root == nullptr)//因为引用,所以下面可以直接new{root = new Node(key,value);return true;}if (root->_key < key){return _InsertR(root->_right, key,value);}else if (root->_key > key){return _InsertR(root->_left, key,value);}else{return false;}}Node* _FindR(Node* root, const K& key)//递归版本{if (root == nullptr){return nullptr;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << ":"<<root->_value<<endl;_InOrder(root->_right);}private:Node* _root;};void test(){BSTree<string, string> dict;dict.InsertR("insert", "插入");dict.InsertR("right", "右边");dict.InsertR("sort", "排序");dict.InsertR("left", "左边");dict.InsertR("date", "日期");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.FindR(str);if (ret){cout << ret->_value << endl;}else{cout << "无此单词" << endl;}}}void test2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& str : arr){auto ret = countTree.FindR(str);if (ret == nullptr){countTree.InsertR(str, 1);}else{ret->_value++;}}countTree.InOrder();}
}

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法系列篇】前缀和

文章目录 前言什么是前缀和算法1.【模板】前缀和1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 【模板】二维前缀和2.1 题目要求2.2 做题思路2.3 Java代码实现 3. 寻找数组的中心下标3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 除自身以外的数组的乘积4.1 题目要求4.2 做题思…

实验九 根文件系统移植

【实验目的】 熟悉根文件系统的目录结构&#xff0c;构建自己的根文件系统 【实验环境】 ubuntu 14.04 发行版FS4412 实验平台交叉编译工具&#xff1a;arm-none-linux-gnueabi- 【注意事项】实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 一、构建自…

XXL-JOB

XXL-JOB是一个分布式的任务调度平台。 目的&#xff1a;为了自动完成特定的任务&#xff0c;在约定的特定时间去执行任务的过程。 原因&#xff1a;在spring中有scheduled&#xff0c;放到业务层代码上面也可以。但是其无法做到高可用、防止重复执行&#xff0c;单机处理极限…

Java项目-苍穹外卖-Day06-微信小程序开发

文章目录 前言1.HttpClienthttpclient是什么入门案例发送GET请求发送POST请求Httpclient工具类 2.微信小程序介绍准备工作注册小程序和完善对应信息下载开发者工具 入门案例 前言 本篇主要是主要是wx小程序开发入门和HttpClient的使用介绍 完成了苍穹外卖用户端的 微信登陆 和…

记录一个用C#实现的windows计时执行任务的服务

记录一个用C#实现的windows计时执行任务的服务 这个服务实现的功能是每天下午六点统计一次指定路径的文件夹大小 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.IO; using Syst…

登录校验-JWT令牌-登陆后下发令牌

目录 思路 接口文档 令牌生成和下发 步骤 具体代码如下 工具类 控制类 测试 前后端联调 思路 令牌生成&#xff1a;登陆成功后&#xff0c;生成JWT令牌&#xff0c;并返回给前端令牌校验&#xff1a;在请求到达服务端后&#xff0c;对令牌进行统一拦截、校验 接口文档…

考研C语言进阶题库——更新41-50题

目录 41.编写程序要求输出整数a和b若a和b的平方和大于100&#xff0c;则输出a和b的平方和&#xff0c;否则输出a和b的和 42.现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&#xff1a;第一项是1/1&#xff0c;第二项是是…

微信开放注册微信小号功能,工作人群福音!

微信&#xff0c;这个坐拥数亿用户的社交巨头&#xff0c;最近终于开放了注册微信小号的功能。这个功能对于需要多个微信账号进行工作的人来说&#xff0c;无疑是一场及时雨&#xff0c;极大地提高了工作便利性。 在之前的版本中&#xff0c;每个微信账号都绑定了一个手机号&am…

改进YOLO系列:5.添加CBAM注意力机制

添加CBAM注意力机制 1. CBAM注意力机制论文2. CBAM注意力机制原理3. CBAM注意力机制的配置3.1common.py配置3.2yolo.py配置3.3yaml文件配置1. CBAM注意力机制论文 论文题目:CBAM: Convolutional Block Attention Module 论文链接:CBAM: Convolutional Block Attenti…

哪个蓝牙耳机打电话比较好,打电话降噪蓝牙耳机推荐

近年来&#xff0c;骨传导耳机的市场表现出了显著的增长&#xff0c;其市场竞争也变得异常激烈&#xff0c;与传统无线耳机相比&#xff0c;“骨传导耳机”的知名度还达不到举世皆知的地步&#xff0c;但对于运动群体以及部分有听力损伤的群体来说&#xff0c;骨传导耳机还是有…

❤ 给自己的mac系统上安装java环境

❤ 给自己的mac系统上安装java环境 &#x1f353; 作为前端工程师如何给自己的mac系统上安装java环境 &#x1f34e; 最近因为自己的一些项目需求&#xff0c;mac电脑上需要安装一些后台的java环境&#xff0c;用来跑后台的java程序&#xff0c;于是从一个前端工程师的角度安…