高级数据结构与算法 | 二叉搜索树(Binary Search Tree)
文章目录
- 二叉搜索树的概念
- 二叉搜索树的作用
- 排序
- 查找
- 实现思路
- 查找
- 插入
- 删除
- 删除、拷贝等
- 代码实现
- K模型
- KV模型
二叉搜索树的概念
二叉搜索树又叫做二叉排序树,他是一个具有以下特性的二叉树。
1. 二叉搜索树的左孩子比父节点小,右孩子比父节点大。
2. 二叉搜索树的左子树的全部节点都小于根节点,右子树的全部节点都大于根节点。
3. 所有节点的左右子树都为二叉搜索树
4. 键值是唯一的,所以二叉搜索树不能有相同的键值。
例如以下数据
int arr[] = { 5, 3, 7, 9, 1, 4 };
按照上面的规则构建成为一个二叉搜索树
根据使用场景的不同,二叉搜索树还分为K模型和KV模型。
K模型:即只有key作为关键码,只需要存储Key即可,关键码即为需要搜索到的值。如STL中的set
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。如STL中的map
二叉搜索树的作用
排序
中序遍历:先遍历左子树,再遍历根节点,再遍历右子树
而二叉搜索树的特性,左子树小于根节点,右子树大于根节点。
所以通过一趟中序遍历,即可获得排序的结果。
查找
对于二叉搜索树,查找是其主要的功能,STL中的map和set底层也是通过平衡二叉搜索树(红黑树)实现的。
二叉搜索树的查找十分简单,键值比根节点大则进入右子树,键值比根节点小则进入左子树,他的思路有点类似于二分查找,平均时间复杂度为O(log2N)
但是上述情况仅限于二叉搜索树为一个完全二叉树, 如果构建时树为有序数列,则二叉搜索树会退化为单支树,时间复杂度则会变为O(N)
如:
int arr[] = { 1, 3, 4, 5, 7, 9 };
如果要解决这个问题,就得为搜索二叉树加上平衡二叉树的属性,也就是我们通常所说的AVL树。
实现思路
查找
查找是二叉搜索树的核心,实现了这一部分后面的删除和插入也可以直接复用这里的部分代码
直接从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树,相同则返回。如果遍历完还没找到,则说明不存在此树中,返回nullptr
Node* Find(const K& key)
{//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (key > cur->_key){cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){cur = cur->_left;}//相同则返回else{return cur;}}//遍历完则说明查找不到,返回falsereturn nullptr;
}
插入
插入前半段就可以复用查找的代码,我们首先要找到我们需要插入的位置(为了保证能够找到父节点还需要用一个指针指向父节点),找到了合适的位置后,我们需要判断当前的键值比父节点大还是比父节点小,来决定应该插入到父节点的左子树还是右子树
例如这里要插入8
查找到所在位置
判断是哪一个子树即完成插入
bool Insert(const K& key)
{//如果此时树为空,则初始化根节点if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = cur;//找到合适的插入位置while (cur){//比根节点大则查找右子树if (key > cur->_key){parent = cur;cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){parent = cur;cur = cur->_left;}//相同则返回false,因为搜索树不能存在相同数据else{return false;}}cur = new Node(key);//判断cur要插入到parent的左子树还是右子树if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;
}
删除
相较于前面几个,删除会稍微复杂一点,因为对于非叶子节点,删除节点会导致搜索二叉树结构的破坏,所以不能直接删除,需要找到一个节点替换原有节点,再将原有节点删除。
删除分为三种情况。
1. 删除叶子节点
对于叶子节点1, 4, 9,我们可以直接的删除,不需要考虑别的。
2. 删除的节点只有一个子树
例如要删除这里的7,如果要保留原有节点,就需要让被删除节点的父节点,指向删除节点的唯一子树。
如:
3. 删除的节点有左右子树
这也是删除里最为麻烦的一块,例如这里的节点5。如果要删除5,就必须要找到一个能够替换5的节点来替换他,然后再将他删除,这样就不会破坏结构。而如何选择这个节点呢?如果要保持原来的结构,那么这个节点就必须要比左子树所有节点大,比右子树所有节点小,而符合的两个节点,则是左子树的最大节点,和右子树的最小节点。
也就是左子树的最右节点和右子树的最左节点。
这两个任意一个即可
首先我们要找到这个节点的位置,如这里选择的是4,我们就需要先找到这个4的位置。然后把4的值覆盖到被删除节点上。由于我们选择的是左子树的最右节点,所以这个节点必定没有右子树,但是不排除没有左子树。(这个图上没有,但是需要考虑有的情况,没有也没关系,反正接的是nullptr),所以需要借用到第二种情况的思路,把4的左子树接到父节点上后,再删除4.
再接着进一步分析,这里的第一二种情况可以合并处理,因为叶子节点的左右子树都为空,即使让父节点指向这两个空节点,也没有任何问题。
还有一种情况,如果删除的结点是根节点,则需要让删除节点的另一子树成为新的根节点。
bool erase(const K& key)
{/*删除有三种情况,一种是删除叶子节点,可以直接删除第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树前两种情况可以合并处理第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。*/Node* cur = _root;Node* parent = cur;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_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;}}delete cur;}//处理只有左子树时 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;}}delete cur;}//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点else{//这里我选取的是左子树的最右节点Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子树的最右节点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替换节点std::swap(cur->_kv, LeftMax->_kv);//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树//并且删除最右节点if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}return true;}}return false;
}
删除、拷贝等
这几个和二叉树的删除拷贝一样,直接从根节点递归处理即可。
代码实现
K模型
#include<iostream>namespace lee
{template<class K>class BSTreeNode{public:BSTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;};template<class K>class BSTree{typedef BSTreeNode<K> Node;typedef BSTree<K> Tree;public:BSTree() : _root(nullptr){}~BSTree(){destory(_root);}BSTree(const Tree& temp) : _root(nullptr){_root = copy(temp._root);}Tree& operator=(const Tree& temp){if (this != &temp){//先清空本树destory(_root);_root = copy(temp._root);}return *this;}//现代写法//Tree& operator=(Tree temp)//{// swap(temp);// return *this;//}void swap(Tree& temp){std::swap(_root, temp._root);}//递归拷贝节点Node* copy(const Node* root){if(!root)return nullptr;Node* temp = new Node(root->_key);temp->_left = copy(root->_left);temp->_right = copy(root->_right);return temp;}//递归销毁全部节点void destory(Node*& root){Node* node = root;if (!root)return;destory(node->_left);destory(node->_right);delete node;node = nullptr;}bool Insert(const K& key){//如果此时树为空,则初始化根节点if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = cur;//找到合适的插入位置while (cur){//比根节点大则查找右子树if (key > cur->_key){parent = cur;cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){parent = cur;cur = cur->_left;}//相同则返回false,因为搜索树不能存在相同数据else{return false;}}cur = new Node(key);//判断cur要插入到parent的左子树还是右子树if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;}bool erase(const K& key){/*删除有三种情况,一种是删除叶子节点,可以直接删除第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树前两种情况可以合并处理第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。*/Node* cur = _root;Node* parent = cur;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树//处理只有右子树时 if (cur->_left == nullptr){//如果当前节点为根节点,则让右子树成为新的根节点if (cur == _root){_root = cur->_left;}else{//判断当前节点是他父节点的哪一个子树if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//处理只有左子树时 else if (cur->_right == nullptr){//如果当前节点为根节点,则让左子树成为新的根节点if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点else{//这里我选取的是左子树的最右节点Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子树的最右节点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替换节点cur->_key = LeftMax->_key;//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树//并且删除最右节点if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}return true;}}return false;}void _InordTravel(Node* root){if (root == nullptr)return;//先遍历左子树_InordTravel(root->_left);//遍历根节点std::cout << root->_key << std::ends;//遍历右子树_InordTravel(root->_right);}//提供给外界的接口,因为外界无法访问私有成员rootvoid InordTravel(){_InordTravel(_root);std::cout << std::endl;}Node* Find(const K& key){//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (key > cur->_key){cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){cur = cur->_left;}//相同则返回else{return cur;}}//遍历完则说明查找不到,返回falsereturn nullptr;}private:Node* _root;};
}
KV模型
#include<iostream>namespace lee
{template<class K, class V>class BSTreeNode{public:BSTreeNode(const K& key = K(), const V& value = V()): _left(nullptr), _right(nullptr), _key(key), _value(value){}BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;typedef BSTree<K, V> Tree;public:BSTree() : _root(nullptr){}~BSTree(){destory(_root);}BSTree(const Tree& temp) : _root(nullptr){_root = copy(temp._root);}Tree& operator=(const Tree& temp){if (this != &temp){//先清空本树destory(_root);_root = copy(temp._root);}return *this;}//现代写法//Tree& operator=(Tree temp)//{// swap(temp);// return *this;//}void swap(Tree& temp){std::swap(_root, temp._root);}Node* copy(const Node* root){if (!root)return nullptr;Node* temp = new Node(root->_key, root->_value);temp->_left = copy(root->_left);temp->_right = copy(root->_right);return temp;}void destory(Node*& root){Node* node = root;if (!root)return;destory(node->_left);destory(node->_right);delete node;node = nullptr;}bool Insert(const K& key, const V& value){//如果此时树为空,则初始化根节点if (_root == nullptr){_root = new Node(key, value);return true;}Node* cur = _root;Node* parent = cur;//找到合适的插入位置while (cur){//比根节点大则查找右子树if (key > cur->_key){parent = cur;cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){parent = cur;cur = cur->_left;}//相同则返回false,因为搜索树不能存在相同数据else{return false;}}cur = new Node(key, value);//判断cur要插入到parent的左子树还是右子树if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;}bool erase(const K& key){/*删除有三种情况,一种是删除叶子节点,可以直接删除第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树前两种情况可以合并处理第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。*/Node* cur = _root;Node* parent = cur;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树//处理只有右子树时 if (cur->_left == nullptr){//如果当前节点为根节点,则让右子树成为新的根节点if (cur == _root){_root= cur->_left;}else{//判断当前节点是他父节点的哪一个子树if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//处理只有左子树时 else if (cur->_right == nullptr){//如果当前节点为根节点,则让左子树成为新的根节点if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点else{//这里我选取的是左子树的最右节点Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子树的最右节点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替换节点std::swap(cur->_kv, LeftMax->_kv);//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树//并且删除最右节点if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}return true;}}return false;}void _InordTravel(Node* root){if (root == nullptr)return;//先遍历左子树_InordTravel(root->_left);//遍历根节点std::cout << root->_key << ':' << root->_value << std::ends;//遍历右子树_InordTravel(root->_right);}//提供给外界的接口,因为外界无法访问私有成员rootvoid InordTravel(){_InordTravel(_root);std::cout << std::endl;}Node* Find(const K& key){//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (key > cur->_key){cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_key){cur = cur->_left;}//相同则返回else{return cur;}}//遍历完则说明查找不到,返回falsereturn nullptr;}private:Node* _root;};
}
高级数据结构与算法 | 二叉搜索树(Binary Search Tree)相关推荐
- C++二叉搜索树(Binary Search Tree)(附完整源码)
二叉搜索树Binary Search Tree node结构体定义 Queue结构体定义 二叉搜索树Binary Search Tree算法的完整源码(定义,实现,main函数测试) node结构体定 ...
- 实现一个二叉搜索树(Binary Search Tree)
文章目录 定义 代码实现 一.Github代码地址 二.节点 三.树实现接口 四.4种遍历方式 五.搜索 六.删除 七.插入 结尾 定义 二叉搜索树(Binary Search Tree),又名二叉排 ...
- 二叉搜索树(Binary Search Tree)(Java实现)
文章目录 1.二叉搜索树 1.1. 基本概念 1.2.树的节点(BinaryNode) 1.3.构造器和成员变量 1.3.公共方法(public method) 1.4.比较函数 1.5.contai ...
- 数据结构与算法:二叉搜索树
✨数据结构与算法:二叉搜索树
- 数据结构与算法-二叉搜索树
二叉搜索树 1.左右子树都是二叉搜索树 2.左子树都比根节点小 3.右子树都比根节点大 (一)编程实现 1.查找 2.插入:递归实现 4.删除 #include <iostream> #i ...
- Python 数据结构与算法——二叉搜索树的实现
class Tree:本身自然需要维护根节点(root),用于指向树的第一个节点 class Tree:root = None class Node:每一个节点都要维护左子树.右子树 class No ...
- 数据结构与算法 / 二叉搜索树(Binary Search Tree)
目录 一.定义 二.性质 三.时间复杂度分析 四.遍历方式 五.源码 一.定义 若左子树不空,则左子树上的所有的节点都小于它的根节点. 若右子树不空,则右子树上的所有的节点都大于它的根节点. 左右子树 ...
- 数据结构与算法(八)二分搜索树(Binary Search Tree)
本文主要包括以下内容: 二分搜索树的基本概念 二分搜索树的基本操作 1. 插入 2. 删除 3. 查询 实现二分搜索树 二分搜索树的不足 二分搜索树的基本概念 二分搜索树(英语:Binary Sear ...
- 【数据结构】二叉查找树/二叉搜索树BST(附相关C++代码)
文章目录 BST相关概念 BST如何添加节点 BST如何遍历 BST如何求最值 BST如何删除节点 BST如何查找节点 如何验证一棵树是BST 本文内容将主要介绍二叉查找树的相关概念,与关于二叉查找树 ...
最新文章
- 什么是ObjCTypes?
- 通过简单例子 | 快速理清 UML类图中六大关系
- winform 统计大量数据重复的元素个数_面试系列:十个海量数据处理方法大总结...
- vue 入门环境搭建
- win8 关于Adobe CS6系列软件Patch覆盖失败的问题(Photoshop CS6、Adobe Illustrator CS6、Adobe Fireworks CS6)...
- 飞机订票系统的html模板,飞机订票系统程序设计
- 去水印+外卖cps小程序源码+视频搭建教程
- template 模板是怎样通过 Compile 编译的
- 【西安石油大学主办|IEEE CPS出版】2020智能控制、测量与信号处理国际学术会议诚邀您投稿参会!...
- 《Webservice的应用与开发》学习笔记 ·001【Web服务、XML文档】
- 毕设-基于Qt的餐饮ERP管理系统
- USB的SIE串行接口引擎
- DBVisualizer导入excel数据
- 制作的动图太大怎么办?如何快速将gif图片缩小?
- 用树莓派控制两盏灯(二)
- 使用ffmpeg将视频切片并加密
- 区块链开发(十四)以太坊go-ethereum客户端查询交易列表探讨
- Python smtp gmail发送邮件(to,cc)
- ORB2单目读代码笔记5--利用灰度质心计算ORB特征点方向,实现旋转不变性
- Linux学习-67-日志服务器设置和日志分析工具(logwatch)安装及使用
热门文章
- SpringBoot 上传多个文件
- SpringBoot 读取资源文件
- Linux Capabilities 入门教程--进阶实战篇
- JDBC、DriverManage、JNDI、数据源(DataSource)、连接池的区别
- 210213阶段三回顾
- 【报错笔记】pom.xml第一行报错,显示红色叉号
- 结构体位域及联合体解析
- Laravel Model 利用 Macroable 为数据模型添加宏能力
- 专业的PDF组件Spire.PDF for .NET V3.9.124发布|支持图标布局
- Java Streams,第 4 部分: 从并发到并行