🌈欢迎来到数据结构专栏~~搜索二叉树
- (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
- 目前状态:大三非科班啃C++中
- 🌍博客主页:张小姐的猫~江湖背景
- 快上车🚘,握好方向盘跟我有一起打天下嘞!
- 送给自己的一句鸡汤🤔:
- 🔥真正的大师永远怀着一颗学徒的心
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
- 🎉🎉欢迎持续关注!
二叉搜索树
- 🌈欢迎来到数据结构专栏~~搜索二叉树
- 一. 概念
- 二. 基本操作
- 🌈查找元素 Search
- 🌈插入 Insert
- 🌈删除 Delete
- 三. 进阶操作 ~ 递归写法
- 🥑递归查找 Search
- 🥑递归插入 Insert
- 🥑递归删除 Delete
- 四. 性能分析
- 五. 二叉搜索树的应用
- 💦Key模型
- 💦Key- Value模型
- 附源码
- `BinarySearchTree.h`
- `test.c`
- 📢写在最后
一. 概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
-
若它的左子树不为空,则 左子树上所有节点的值都小于根节点的值
-
若它的左子树不为空,则 右子树上所有节点的值都大于根节点的值
-
它的左右子树也分别为二叉搜索树
二. 基本操作
🌈查找元素 Search
- 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
- 最多查找高度次,走到到空,还没找到,这个值不存在
//查找
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;
}
}
}
🌈插入 Insert
思路如下:
- 树为空,则直接新增节点,直接插入root指针即可
- 因为不能插入重复的元素,并且每次插入都是要定位到空节点的位置;定义一个
cur
从root开始,插入的元素比当前位置元素小就往左走,比当前位置元素大就往右走,直到为空; - 再定义一个
parent
记录cur
的前一个位置,最后判断cur是parent的左子树or右子树
动画演示:
//相同的
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;
}
🌈删除 Delete
删除有三种情况:
- 无牵无挂型:如果是叶子结点,直接置空并链接到空指针
- 独生子女型:只有一个子节点,删除自己本身,并链接子节点和父节点
- 替罪羊型:寻找出右子树最小节点、左子树最大节点,其节点可以作为交换节点和删除结点进行交换,交换后删除交换节点、交换节点要么没有孩子,要么只有一个孩子可以直接删除
其实在代码层面第一种情况可以归类到第二种情况(没有左孩子,就链接上右孩子)
在代码层面实际上是四种:
- 左为空
- 右为空
- 左右都为空(细节很多看下图)
- cur是跟节点位置(特殊情况移动
root
)
//删除
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
{
//找到了,开始删除,有三种情况
//1、左为空
//2、右为空
//3、左右都不为空
//4、删除跟root 要移动root(特殊情况)
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else
{
//左右都不为空 —— 替换法删除
//找到右树最小节点进行替换
Node* min = cur->_right;
Node* minparent = nullptr;
while (min->_left)
{
minparent = min;
min = min->_left;
}
swap(cur->_key, min->_key);
//注意min的右子树还有连接节点的可能
//和判断min在哪边的问题?
if (minparent->left = min)
{
minparent->_left = min->_right;
}
else
{
minparent->_right = min->_right;
}
delete min;
}
return true;
}
}
return false;
}
三. 进阶操作 ~ 递归写法
🥑递归查找 Search
唤起递归的记忆吧
void _FindR(Node* root, const K& key)
{
//根为空的情况下
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _FindR(root->_right);
}
else if (root->_key > key)
{
return _FindR(root->_left);
}
else
{
return true;
}
}
🥑递归插入 Insert
要注意,这里有“神之一手”
void _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
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;
}
}
🥑递归删除 Delete
神之操作:root = root->_left
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* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(min->_key, root->_key);
return _Erase(root->_right)
}
delete del;
return true;
}
}
四. 性能分析
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:
l
o
g
2
N
log_2 N
log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:
N
2
\frac{N}{2}
2N
五. 二叉搜索树的应用
💦Key模型
key
的搜索模型,判断关键字在不在
- 刷卡进宿舍楼
- 检测一篇英文文档中单词拼写是否正确
以上都是把全部相关的资料都插入到一颗搜索树中,然后开始寻找,判断在不在
💦Key- Value模型
KV
模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>
的键值对。
- 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
- 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{
BSTNode(const K& key = K(), const V& value = V())
: _pLeft(nullptr), _pRight(nullptr), _key(key), _Value(value)
{}
BSTNode<T>* _pLeft;
BSTNode<T>* _pRight;
K _key;
V _value
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
typedef Node* PNode;
public:
BSTree() : _pRoot(nullptr) {}
PNode Find(const K& key);
bool Insert(const K& key, const V& value)
bool Erase(const K& key)
private:
PNode _pRoot;
}
void TestBSTree3()
{
// 输入单词,查找单词对应的中文翻译
BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("left", "左边、剩余");
dict.Insert("right", "右边");
dict.Insert("sort", "排序");
// 插入词库中所有单词
string str;
while (cin >> str)
{
BSTreeNode<string, string>* ret = dict.Find(str);
if (ret == nullptr)
{
cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
}
else
{
cout << str << "中文翻译:" << ret->_value << endl;
}
}
}
附源码
BinarySearchTree.h
#pragma once
#include<iostream>
using namespace std;
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)
{}
};
//class BinarySearchTree
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//插入
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;
}
}
}
//删除
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
{
//找到了,开始删除,有三种情况
//1、左为空
//2、右为空
//3、左右都不为空
//4、删除跟root 要移动root(特殊情况)
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else
{
//左右都不为空 —— 替换法删除
//找到右树最小节点进行替换
Node* min = cur->_right;
Node* minparent = nullptr;
while (min->_left)
{
minparent = min;
min = min->_left;
}
swap(cur->_key, min->_key);
//注意min的右子树还有连接节点的可能
//和判断min在哪边的问题?
if (minparent->_left = min)
{
minparent->_left = min->_right;
}
else
{
minparent->_right = min->_right;
}
delete min;
}
return true;
}
}
return false;
}
//这样就能避免this指针问题,因为递归必须显示给参数
void InOrder()
{
_InOrder(_root);//这样就可以使用_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);
}
~BSTree()
{
_Destory(_root);
}
//BSTree()
//{}
//C++11的用法:强制编译器生成默认构造
BSTree() = default;
//拷贝构造
BSTree(const BSTree<K>& t)
{
_root = _Copy(t._root);
}
//t2 = t1
BSTree<K>& operator = (BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
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 _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(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* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(min->_key, root->_key);
return _Erase(root->_right);//防止找不到key的情况
}
delete del;
return true;
}
}
void _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
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;
}
}
void _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 == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
void TestBSTree1()
{
BSTree<int> t;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
t.Insert(e);
}
//排序+去重
t.InOrder();
t.Erase(3);
t.InOrder();
t.Erase(6);
t.InOrder();
}
void TestBSTree2()
{
BSTree<int> t;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
t.Insert(e);
}
BSTree<int> copy = t;
copy.InOrder();
t.InOrder();
}
}
test.c
#include "BinarySearchTree.h"
int main()
{
//TestBSTree1();
/*TestBSTree2();*/
TestBSTree3();
return 0;
}
📢写在最后
多多更新