
🦌云深麋鹿
专栏 :C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了 多态,接下来这篇文章让我们进入到 二叉搜索树 的学习,体会新的设计思路吧~
放个目录
- [一 概念](#一 概念)
- [二 性能分析](#二 性能分析)
-
- [2.1 二叉搜索树性能分析](#2.1 二叉搜索树性能分析)
- [2.2 二分查找](#2.2 二分查找)
- [三 二叉搜索树的插入](#三 二叉搜索树的插入)
-
- [3.1 上代码](#3.1 上代码)
- [3.2 测试](#3.2 测试)
- [四 二叉搜索树的查找](#四 二叉搜索树的查找)
-
- [4.1 上代码](#4.1 上代码)
- [4.2 测试](#4.2 测试)
- [五 二叉搜索树的删除](#五 二叉搜索树的删除)
-
- [5.1 四种情况](#5.1 四种情况)
- [5.2 上代码](#5.2 上代码)
- [5.3 测试](#5.3 测试)
- [六 二叉搜索树的其他实现](#六 二叉搜索树的其他实现)
-
- [6.1 析构函数](#6.1 析构函数)
-
- [6.1.1 _destory](#6.1.1 _destory)
- [6.1.2 析构函数](#6.1.2 析构函数)
- [6.1.3 测试](#6.1.3 测试)
- [6.2 拷贝构造](#6.2 拷贝构造)
-
- [6.2.1 默认构造](#6.2.1 默认构造)
- [6.2.2 上代码](#6.2.2 上代码)
- [6.2.3 测试](#6.2.3 测试)
- [6.3 赋值重载](#6.3 赋值重载)
-
- [6.3.1 代码](#6.3.1 代码)
- [6.3.2 测试](#6.3.2 测试)
- [七 二叉搜索树key和key/value使用场景](#七 二叉搜索树key和key/value使用场景)
-
- [7.1 key搜索场景](#7.1 key搜索场景)
- [7.2 key/value搜索场景](#7.2 key/value搜索场景)
一 概念
二叉搜索树⼜称⼆叉排序树。
性质
- 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
- 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
- 它的左右子树也分别为⼆叉搜索树。
- ⼆叉搜索树中可以⽀持插入相等的值,也可以不⽀持插入相等的值,后续会提到具体实现。
二 性能分析
2.1 二叉搜索树性能分析
- 最优情况下,⼆叉搜索树为完全⼆叉树,其高度(即时间复杂度)为: logN。
- 最坏情况下,二叉搜索树为单枝,其高度(即时间复杂度)为: N。
- 总结:⼆叉搜索树增删查改时间复杂度为 O(N)
2.2 二分查找
二分查找时间复杂度为logN,但是有两⼤缺陷:
- 需要存储在⽀持下标随机访问的结构中,并且有序。
- 插入和删除数据效率很低(因为存储在下标随机访问的结构中,插入和删除数据⼀般需要挪动数据)。
三 二叉搜索树的插入
- 树为空。
- 树不为空,按⼆叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走。
- 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走。
3.1 上代码
cpp
bool insert(const K& key) {
if (!_root) {
_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->_left = cur;
}else{
parent->_right = cur;
}
return true;
}
当前不支持插入已出现过的值。
3.2 测试
cpp
wyzy::BSTree<int> tree;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e:a) {
tree.insert(e);
}
tree.inOrder();
运行:

四 二叉搜索树的查找
4.1 上代码
cpp
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;
}
跟插入类似的逻辑,只不过这里找到相等的就可以结束了。
4.2 测试
插入代码同上。
cpp
while (cin >> x) {
if (tree.find(x)) {
cout << "find it" << endl;
}
else {
cout << "not find it" << endl;
}
}
运行:

测试涉及operator bool把iostream对象转换成bool值。
五 二叉搜索树的删除
5.1 四种情况
- 要删除结点N左右孩子均为空。
- 要删除的结点N左孩子为空,右孩子结点不为空。
- 要删除的结点N右孩子为空,左孩子结点不为空。
- 要删除的结点N左右孩子结点均不为空。
对应删除逻辑: - 直接删除。
- 把右孩子交给父结点。
- 把左孩子交给父结点。
- 找 左子树的最大结点/右子树的最小结点 替代被删位置。
5.2 上代码
cpp
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 {
break;
}
}
if (cur) {
if (!cur -> _left) {
if (!parent) {
_root = cur->_right;
}else if (cur == parent->_left) {
parent->_left = cur->_right;
}else{
parent->_right = cur->_right;
}
delete cur;
}
else if (!cur->_right) {
if (!parent) {
_root = cur->_left;
}else if (cur == parent->_left) {
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
delete cur;
}
else {
Node* mR_parent = cur;
Node* minRight = cur->_right;
while (minRight-> _left) {
mR_parent = minRight;
minRight = minRight->_left;
}
swap(cur->_key, minRight->_key);
if (mR_parent == cur) {
mR_parent->_right = minRight->_right;
}
else {
mR_parent->_left = minRight->_right;
}
delete minRight;
}
return true;
}
return false;
}
5.3 测试
插入代码依旧。
cpp
for (auto e : a) {
tree.erase(e);
tree.inOrder();
}
运行:

六 二叉搜索树的其他实现
6.1 析构函数
6.1.1 _destory
需要有个递归函数我们另外写一个_destory:
cpp
void _destory(Node* root) {
if(!root){
return;
}
_destory(root->_left);
_destory(root->_right);
delete root;
}
6.1.2 析构函数
析构里调用_destory:
cpp
~BSTree() {
_destory(_root);
_root = nullptr;
}
6.1.3 测试
依旧上面那棵树:
cpp
wyzy::BSTree<int> tree;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e:a) {
tree.insert(e);
}
调试:

画出来这样:

调试:


6.2 拷贝构造
6.2.1 默认构造
这里需要显式定义默认构造。
走初始化列表:
cpp
BSTree(){}
或者强制默认生成:
cpp
BSTree() = default;
6.2.2 上代码
(1)辅助函数_constructor
cpp
void _constructor(Node* root) {
if(!root){
return;
}
insert(root->_key);
_constructor(root->_right);
_constructor(root->_left);
}
老生常谈的递归了。
(2)_constructor
cpp
BSTree(const BSTree& tree) {
_constructor(tree._root);
}
6.2.3 测试
cpp
wyzy::BSTree<int> tree1;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a) {
tree1.insert(e);
}
wyzy::BSTree<int> tree2(tree1);
tree1.inOrder();
tree2.inOrder();
调试:

运行:

6.3 赋值重载
6.3.1 代码
复用。
cpp
BSTree& operator=(const BSTree& tree) {
if (tree._root != nullptr) {
_destory(_root);
_root = nullptr;
}
_constructor(tree._root);
return *this;
}
6.3.2 测试
cpp
wyzy::BSTree<int> tree1;
wyzy::BSTree<int> tree2;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a) {
tree1.insert(e);
}
tree2 = tree1;
tree1.inOrder();
tree2.inOrder();
调试:

运行:

七 二叉搜索树key和key/value使用场景
7.1 key搜索场景
- 识别车牌号。
7.2 key/value搜索场景
- 简单中英字典。
- 商场停车场。
- 统计文章单词出现次数。
二叉搜索树 的学习就到这里,下一篇我们上新的容器 map&set ,很快会更出来~


