今天,带来二叉搜索树的讲解。

文中不足错漏之处望请斧正!


是什么

二叉搜索树(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

递归里我们不能用替换法直接覆盖删了,但是思路一样,还是需要替换,不过绕了个弯子。

我们可以替换,让左子树最大/右子树最小,也可以说是最左/最右结点作根,原本的根换下去。

有两个点:

  1. 最左结点/最右结点必然有一侧是空的,那就可以直接删除或托孤
  2. 替换后不符合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-二叉搜索树】强,但没貌似还不够?相关推荐

  1. 【C++进阶:二叉树进阶】二叉搜索树的操作和key模型、key/value模型的实现 | 二叉搜索树的应用 | 二叉搜索树的性能分析

    [写在前面] 从这里开始 C++ 的语法就告一段落了.二叉树在前面的数据结构和算法初阶中就讲过,本文取名为二叉树进阶是因为: 二叉树进阶有着承上启下的作用,承上就是借助二叉搜索树,对二叉树初阶部分进行 ...

  2. C++初阶学习————二叉树进阶(二叉搜索树)

    二叉树进阶 二叉搜索树的概念 二叉搜索树的操作 基本框架 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 整体代码 循环写法 递归写法 二叉搜索树的应用 二叉搜索树的性能分析 前面的文章介绍过二 ...

  3. 【C++】二叉树进阶(二叉搜索树)

    文章目录 前言 1.二叉搜索树 1-1. 二叉搜索树概念 2.二叉搜索树操作 2-1.树和节点的基本框架 2-2.二叉搜索树的查找 2-3.中序遍历 2-4.二叉搜索树的插入 2-5.二叉搜索树的删除 ...

  4. C++(第十三篇):二叉搜索树(二叉树进阶、二叉搜索树的结构、应用及模拟实现)

  5. 二叉树进阶--二叉搜索树

    目录 1.二叉搜索树 1.1 二叉搜索树概念 1.2 二叉搜索树操作 1.3 二叉搜索树的实现 1.4 二叉搜索树的应用 1.5 二叉搜索树的性能分析 2.二叉树进阶经典题: 1.二叉搜索树 1.1 ...

  6. 【二叉树进阶】红黑树(Red Black Tree) - 平衡二叉搜索树

    文章目录 一.红黑树的概念 二.红黑树的性质 2.1 红黑树和AVL树效率对比 三.红黑树的结构(KV模型) 四.红黑树的插入 4.1 插入节点 4.2 平衡化操作(难点) 4.2.1 情况一 4.2 ...

  7. 二叉搜索树(二叉树进阶)

    文章目录 二叉搜索树的概念 二叉搜索树的实现 二叉搜索树的节点 构造函数 拷贝构造函数 赋值运算符重载 析构函数 插入函数 非递归实现 递归实现 删除函数 非递归实现 递归实现 查找函数 非递归实现 ...

  8. 6-12 二叉搜索树的操作集

    6-12 二叉搜索树的操作集(30 分) 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); Bin ...

  9. 刻意练习:LeetCode实战 -- Task24. 恢复二叉搜索树

    背景 本篇图文是LSGO软件技术团队组织的 第二期基础算法(Leetcode)刻意练习训练营 的打卡任务.本期训练营采用分类别练习的模式,即选择了五个知识点(数组.链表.字符串.树.贪心算法),每个知 ...

最新文章

  1. tp3.2 连接两个数据库
  2. phpcms开启、关闭在线编辑模板的方法
  3. IDE之VS:Visual Studio2017版本安装图文教程之详细攻略
  4. p沟道mos管导通条件_通俗易懂:MOS管基本知识(快速入门)
  5. Gateway网关-路由的过滤器配置
  6. Pretty girl,你一定要去旅行
  7. P3466-[POI2008]KLO-Building blocks【Treap】
  8. jsk Star War (线段树维护区间最小最大值 + 二分)
  9. 小米C++开发 面试 准备阶段和部分真题
  10. 红帽Linux7怎么修改网卡名称,新安装的Centos 7系统怎么将网卡名称改为eth0?
  11. [从零学习汇编语言] - 计算机发展历史
  12. HDU 4269 Defend Jian Ge 解题报告
  13. APP支付宝提现和微信提现之服务端接入
  14. Linux 删除多余内核
  15. 2021年软考时间阶段
  16. Support Vector Machine学习笔记
  17. 如何在一个frame中调用另一个frame中的javascript函数
  18. S3C6410裸机SD卡驱动(SDIO模式)
  19. 【玩转华为云】手把手教你利用ModelArts识别偶像的声音
  20. TCP和UDP的实现

热门文章

  1. VS code代码颜色插件
  2. 移动端Monkey测试
  3. Ubuntu升级后进入不了图形界面
  4. 【深度学习】——分类损失函数、回归损失函数、交叉熵损失函数、均方差损失函数、损失函数曲线、
  5. 来自山西机器人乐队_全球首个中国风机器人乐队亮相演出,人工智能时代已经到来!...
  6. k30pro杀进程严重怎么解决_一换季,绿植盆栽容易出问题,叶子最明显,干尖严重怎么解决...
  7. luogu P3792 由乃与大母神原型和偶像崇拜
  8. php怎么做企业号切换,企业号转换接口
  9. 学编程到底需要什么计算机基础知识?
  10. HDU_1114 PiggyBank ( dp | 完全背包问题 )