【C++进阶3-二叉搜索树】强,但没貌似还不够?
今天,带来二叉搜索树的讲解。
文中不足错漏之处望请斧正!
是什么
二叉搜索树(Binary Search Tree)又称二叉排序树。
它可以是一棵空树,也可以是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值
- 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值
- 它的左右子树也分别为二叉搜索树
实现
二叉搜索树有两种搜索模型——Key搜索模型和<Key, Value>搜索模型。
Key搜索模型
Key模型的BST每个结点内存一个Key值,即用Key作为关键码,Key本身就是搜索需要找到的值。
结构
template<class K>
struct BSTreeNode
{BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}BSTreeNode<K>* _left = nullptr;BSTreeNode<K>* _right = nullptr;K _key;
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:Node* _root = nullptr;
};
Insert
思路:key小往左走,key大往右走。
参考代码
//依照key值找插入位置(BST元素不重复)bool Insert(const K& key){if(_root == nullptr){_root = new Node(key);return true;}Node* cur = _root, *parent = nullptr;while(cur){if(cur->_key < key){parent = cur;cur = cur->_right;}else if(key < cur->_key){parent = cur;cur = cur->_left;}elsereturn false;}cur = new Node(key);if(parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;}
Erase
思路:
1. 要删的是根/没有孩子的结点
:直接删
2. 要删的有左孩子/右孩子
:将左/右孩子托付给自己的父。
3. 要删的有左右孩子
替换法删除:找一个合适的结点替换删除。
找谁?
左子树最大 / 右子树最小都可以。
- BST规则:左子树的全部结点都比右子树小,也可以说右子树的全部结点都比左子树的大。
- 左子树最大的上来作根:比左子树的都大,比右子树的都小()
- 右子树最小的上来作根:比右子树的都小,比左子树的都大(BST规则:左子树的全部都比右子树小)
参考代码
bool Erase(const K& key) //删除 = 删除 + 链接{if(Empty()) return false;Node* cur = _root, *parent = nullptr;while(cur){if(cur->_key < key){parent = cur;cur = cur->_right;}else if(key < cur->_key){parent = cur;cur = cur->_left;}else break;}if(cur == nullptr) return false;//走到这,cur即要删除的结点//要删的是根 / 要删的结点没有孩子都可以直接包含在这种条件内(复用代码)//2. 要删的结点有右孩子 ==> 将右子树托孤if(cur->_left == nullptr){if(cur == _root){_root = cur->_right;}else{if(cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}//2. 要删的结点有左孩子 ==> 将左子树托孤else if(cur->_right == nullptr){if(cur == _root){_root = cur->_left;}else{if(cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}//3. 要删的结点有左右孩子 ==> 替换法删除else{Node* minRight = cur->_right, *parent = cur;while(minRight->_left) //此分支内,minRight肯定不为空,可以直接解引用{parent = minRight;minRight = minRight->_left;}//覆盖掉要删的cur->_key = minRight->_key; if(minRight == parent->_left)parent->_left = minRight->_right;elseparent->_right = minRight->_right;//我已经去作老大了,这里得清理干净delete minRight;return true;}return false;}
InsertR
可以用递归实现一下,有个很妙的点。
我们插入和删除最大的难点就是链父,插入了,我的父是谁,删除了,我的父是谁?
在递归里,这样的场景我们可以用引用做参数——使得root是父亲的左/右孩子的引用。
bool _InsertR(Node*& root, const K& key){//最大的问题是链父,root作引用,是父结点的left/rightif(root == nullptr){root = new Node(key); //root是父结点的left/right的引用,这一步赋值相当于链父了return true;}if(root->_key < key)return _InsertR(root->_right, key);else if(key < root->_key)return _InsertR(root->_left, key);elsereturn false;}
EraseR
递归里我们不能用替换法直接覆盖删了,但是思路一样,还是需要替换,不过绕了个弯子。
我们可以替换,让左子树最大/右子树最小,也可以说是最左/最右结点作根,原本的根换下去。
有两个点:
- 最左结点/最右结点必然有一侧是空的,那就可以直接删除或托孤
- 替换后不符合BST规则:整体来说不符合,但是从局部来说还符合
局部符合有什么用?
局部符合我们就可以在局部删!
bool _EraseR(Node*& root, const K& key){if(root == nullptr) return false;if(root->_key < key)return _EraseR(root->_right, key);else if(key < root->_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* minRight = root->_right;while(minRight->_left)minRight = minRight->_left;//交换法:直接找key删,没法删,找替换节点交换,交换后可以删替换节点//(因为是最左/最右结点,一定有一侧是空的,也就可以托孤)swap(root->_key, minRight->_key);//交换后,root为根的子树不符合BST,无法递归,会找不到key//但是,因为找来的替换节点是右子树的最左节点,替换后右子树保持BST//就可以转换成到root->_right为根的子树找,而交换后,我们可以用托孤的方式删除minRight/maxLeft_EraseR(root->_right, key);}delete del;return true;}}
子函数
我们把这些需要传递私有成员的函数都作为子函数,或是弄一层函数重载——外面调用时是无法访问私有成员,所以无法传参。
*其实可以写一个GetRoot这种函数给外面提供,但是暴露底层了,不好
子函数:
//外面可以直接调用void InOrder(){_InOrder(_root); }//封一层void _InOrder(Node* root){ ... }
函数重载:
//给外面调用void InOrder(){InOrder(_root); }//给内部调用void _InOrder(Node* root){ ... }
还有一些拷贝构造和析构什么的,完善一下就好了。
*整体代码在文末
<Key, Value>搜索模型
KV模型的BST每个结点内存一个pair键值对,即用pair中的Key作为关键码,Key对应的Value是需要找到的值。
关于键值对,它存放了一个键和相应值的对,键用于在树中进行查找,而值则相当于查找键所得到的结果。
C++中的pair类模板是一种将两个值组合成一个单元的数据结构。这对于需要将一对值作为一个单元处理的情况非常有用。pair模板具有两个模板参数,一个表示第一个值的类型,另一个表示第二个值的类型。例如,pair<int, string>是一个由int和string类型组成的单元。我们此处用它来将key和value组合成一个单元,实现KV搜索模型。
实现
实现上我们从简,只玩儿精华,看到它和K模型的区别即可。
//KV查找模型:key value
namespace KV
{template<class K, class V>
struct BSTreeNode
{BSTreeNode<K, V>* _left = nullptr;BSTreeNode<K, V>* _right = nullptr;K _key;V _val;BSTreeNode(const K& key, const V& val):_left(nullptr),_right(nullptr),_key(key),_val(val){}
};template<class K, class V>
class BSTree
{public:typedef BSTreeNode<K, V> Node;BSTree():_root(nullptr){}bool Insert(const K& key, const V& val){if(_root == nullptr){_root = new Node(key, val);return true;}Node* cur = _root, *parent = nullptr;while(cur){if(cur->_key < key){parent = cur;cur = cur->_right;}else if(key < cur->_key){parent = cur;cur = cur->_left;}elsereturn false;}cur = new Node(key, val);if(parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;}void InOrder(){_InOrder(_root); //递归必须要传root:子函数内部传参,不想写GetRoot暴露rootcout << endl;}Node* Find(const K& key){Node* cur = _root;while(cur){if(cur->_key == key)return cur;else if(cur->_key < key)cur = cur->_right;elsecur = cur->_left;}return nullptr;}void _InOrder(Node* root){if(root == nullptr) return;_InOrder(root->_left);cout << "["<< root->_key<< ", "<< root->_val<< "]" << endl;_InOrder(root->_right);}
private:Node* _root = nullptr;
};
BST性能分析
最优情况
是或接近完全二叉树,据key搜索一次需要比较logN次,时间复杂度为O(N)。
还是很不错的哦!
最差情况
是或接近单支树,据key搜索一次需要比较N次,时间复杂度为O(N)。
但是会退化,感觉不够强……
K模型BST的整体参考代码
整体参考代码
//Key搜索模型
template<class K>
struct BSTreeNode
{BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}BSTreeNode<K>* _left = nullptr;BSTreeNode<K>* _right = nullptr;K _key;
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree():_root(nullptr){}~BSTree(){Destroy(_root);}BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}//依照key值找插入位置(BST元素不重复)bool Insert(const K& key){if(_root == nullptr){_root = new Node(key);return true;}Node* cur = _root, *parent = nullptr;while(cur){if(cur->_key < key){parent = cur;cur = cur->_right;}else if(key < cur->_key){parent = cur;cur = cur->_left;}elsereturn false;}cur = new Node(key);if(parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;}void InOrder(){_InOrder(_root); //递归必须要传root:子函数内部传参,不想写GetRoot暴露rootcout << endl;}bool Find(const K& key){Node* cur = _root;while(cur){if(cur->_key == key)return true;else if(cur->_key < key)cur = cur->_right;elsecur = cur->_left;}return false;}bool Empty() { return _root == nullptr;}bool Erase(const K& key) //删除 = 删除 + 链接{if(Empty()) return false;Node* cur = _root, *parent = nullptr;while(cur){if(cur->_key < key){parent = cur;cur = cur->_right;}else if(key < cur->_key){parent = cur;cur = cur->_left;}else break;}if(cur == nullptr) return false;if(cur->_left == nullptr){if(cur == _root){_root = cur->_right;}else{if(cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if(cur->_right == nullptr){if(cur == _root){_root = cur->_left;}else{if(cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else{Node* minRight = cur->_right, *parent = cur;while(minRight->_left) //此分支内,minRight肯定不为空{parent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key; //左子树最大 / 右子树最小都符合if(minRight == parent->_left)parent->_left = minRight->_right;elseparent->_right = minRight->_right;delete minRight;return true;}return false;}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}bool FindR(const K& key){return _FindR(_root, key);}private:void Destroy(Node* root){if(root == nullptr) return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if(root == nullptr) return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void _InOrder(Node* root){if(root == nullptr) return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}bool _InsertR(Node*& root, const K& key){//最大的问题是链父,root作引用,是父结点的left/rightif(root == nullptr){root = new Node(key); //root是父结点的left/right的引用,这一步赋值相当于链父了return true;}if(root->_key < key)return _InsertR(root->_right, key);else if(key < root->_key)return _InsertR(root->_left, key);elsereturn false;}bool _EraseR(Node*& root, const K& key){if(root == nullptr) return false;if(root->_key < key)return _EraseR(root->_right, key);else if(key < root->_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* minRight = root->_right;while(minRight->_left)minRight = minRight->_left;//交换法:直接找key删,没法删,找替换节点交换,交换后可以删替换节点//(因为是最左/最右结点,一定有一侧是空的,也就可以托孤)swap(root->_key, minRight->_key);_EraseR(root->_right, key);}delete del;return true;}}bool _FindR(Node*& root, const K& key){if(root == nullptr) return false;if(root->_key < key)return _FindR(root->_right, key);else if(key < root->_key)return _FindR(root->_left, key);elsereturn true;}Node* _root = nullptr;
};
今天的分享就到这里了,感谢您能看到这里。
这里是培根的blog,期待与你共同进步!
【C++进阶3-二叉搜索树】强,但没貌似还不够?相关推荐
- 【C++进阶:二叉树进阶】二叉搜索树的操作和key模型、key/value模型的实现 | 二叉搜索树的应用 | 二叉搜索树的性能分析
[写在前面] 从这里开始 C++ 的语法就告一段落了.二叉树在前面的数据结构和算法初阶中就讲过,本文取名为二叉树进阶是因为: 二叉树进阶有着承上启下的作用,承上就是借助二叉搜索树,对二叉树初阶部分进行 ...
- C++初阶学习————二叉树进阶(二叉搜索树)
二叉树进阶 二叉搜索树的概念 二叉搜索树的操作 基本框架 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 整体代码 循环写法 递归写法 二叉搜索树的应用 二叉搜索树的性能分析 前面的文章介绍过二 ...
- 【C++】二叉树进阶(二叉搜索树)
文章目录 前言 1.二叉搜索树 1-1. 二叉搜索树概念 2.二叉搜索树操作 2-1.树和节点的基本框架 2-2.二叉搜索树的查找 2-3.中序遍历 2-4.二叉搜索树的插入 2-5.二叉搜索树的删除 ...
- C++(第十三篇):二叉搜索树(二叉树进阶、二叉搜索树的结构、应用及模拟实现)
- 二叉树进阶--二叉搜索树
目录 1.二叉搜索树 1.1 二叉搜索树概念 1.2 二叉搜索树操作 1.3 二叉搜索树的实现 1.4 二叉搜索树的应用 1.5 二叉搜索树的性能分析 2.二叉树进阶经典题: 1.二叉搜索树 1.1 ...
- 【二叉树进阶】红黑树(Red Black Tree) - 平衡二叉搜索树
文章目录 一.红黑树的概念 二.红黑树的性质 2.1 红黑树和AVL树效率对比 三.红黑树的结构(KV模型) 四.红黑树的插入 4.1 插入节点 4.2 平衡化操作(难点) 4.2.1 情况一 4.2 ...
- 二叉搜索树(二叉树进阶)
文章目录 二叉搜索树的概念 二叉搜索树的实现 二叉搜索树的节点 构造函数 拷贝构造函数 赋值运算符重载 析构函数 插入函数 非递归实现 递归实现 删除函数 非递归实现 递归实现 查找函数 非递归实现 ...
- 6-12 二叉搜索树的操作集
6-12 二叉搜索树的操作集(30 分) 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); Bin ...
- 刻意练习:LeetCode实战 -- Task24. 恢复二叉搜索树
背景 本篇图文是LSGO软件技术团队组织的 第二期基础算法(Leetcode)刻意练习训练营 的打卡任务.本期训练营采用分类别练习的模式,即选择了五个知识点(数组.链表.字符串.树.贪心算法),每个知 ...
最新文章
- tp3.2 连接两个数据库
- phpcms开启、关闭在线编辑模板的方法
- IDE之VS:Visual Studio2017版本安装图文教程之详细攻略
- p沟道mos管导通条件_通俗易懂:MOS管基本知识(快速入门)
- Gateway网关-路由的过滤器配置
- Pretty girl,你一定要去旅行
- P3466-[POI2008]KLO-Building blocks【Treap】
- jsk Star War (线段树维护区间最小最大值 + 二分)
- 小米C++开发 面试 准备阶段和部分真题
- 红帽Linux7怎么修改网卡名称,新安装的Centos 7系统怎么将网卡名称改为eth0?
- [从零学习汇编语言] - 计算机发展历史
- HDU 4269 Defend Jian Ge 解题报告
- APP支付宝提现和微信提现之服务端接入
- Linux 删除多余内核
- 2021年软考时间阶段
- Support Vector Machine学习笔记
- 如何在一个frame中调用另一个frame中的javascript函数
- S3C6410裸机SD卡驱动(SDIO模式)
- 【玩转华为云】手把手教你利用ModelArts识别偶像的声音
- TCP和UDP的实现
热门文章
- VS code代码颜色插件
- 移动端Monkey测试
- Ubuntu升级后进入不了图形界面
- 【深度学习】——分类损失函数、回归损失函数、交叉熵损失函数、均方差损失函数、损失函数曲线、
- 来自山西机器人乐队_全球首个中国风机器人乐队亮相演出,人工智能时代已经到来!...
- k30pro杀进程严重怎么解决_一换季,绿植盆栽容易出问题,叶子最明显,干尖严重怎么解决...
- luogu P3792 由乃与大母神原型和偶像崇拜
- php怎么做企业号切换,企业号转换接口
- 学编程到底需要什么计算机基础知识?
- HDU_1114 PiggyBank ( dp | 完全背包问题 )