1.二叉搜索树
二叉搜索树,又叫搜索二叉树或排序二叉树,我们已经知道,树形结构的意义不是存数据,数据之间的关联更需要我们关注。
(1)相关特性
a.二叉搜索树的存储规律是左子树的值小于根的值,右子树的值大于根的值,当我们中序遍历一颗二叉搜索树时,就会得到由小到大排序的数组(用递归思想理解)。
b.二叉搜索树一般不允许数据冗余,即整棵树不能出现两个结点存的值一样,在key-value模型中key的值不能一样,就算value不相同。
c.简单的二叉搜索树本身存在一些缺陷,当我们对一些相同的数据以不同顺序插入树当中,会导致根不一样,整棵树的形状不一样,在极端情况下树会退化成链表,如1,2,3,4,5,6,7,8,9这几个数字,如果先插入1,那么根的左边就是空的,数据全部存到右子树了。所以在搜索二叉树的基础上,还引申出AVL树、红黑树(平衡二叉搜索树),它们在树长歪的时候会旋转,防止退化。set的底层就引入了红黑树,不只是普通的搜索二叉树
AVL树:树的左右子树高度差不超过1
红黑树:左右子树高度差不超过2倍
d.只需高度次就能找到一个值,但是没有人知道这棵树有没有退化,加上时间复杂度按最坏情况考虑,所以普通的搜索二叉树搜索是O(N),而AVL树、红黑树是O(logN)
(2)实现的功能:排序 + 去重 + 查找
查找算法的发展:
暴力查找、排序 + 二分查找(底层是数组,就算有较快的排序,但插入删除的代价都很大)
->搜索树(二叉搜索树,但效率最坏是O(N))
->平衡树(AVL树、红黑树,防止二叉搜索树的退化,搜索时间复杂度是O(logN),但是它们只在内存中搜索很快)
->多叉平衡树(B树系列,防止一棵树的高度过大)
2.key、key-value模型的理解
key和key-value模型是二叉搜索树中存值的两种模型
key模型:key模型通俗来讲就是存值,将一个个值按照大小存到这棵树上(这个值可能是内置类型,也可以是自定义类型)。当我们搜索key时,就类似于门禁系统一样,是便利整棵树的值,看树上有没有这个值,也可称为直接比对。
key-value模型:key-value模型存的是两个值,通过一个值(key)找另一个值(value),可以理解为我们先像key模型那样存数据,在树中我们是按key来查找数据,不同的是,每个结点中还有一个value,和key一一对应。字典就是如此,如key是英文,value是中文,用英文搜索,可以找到中文;我们也可以用车库的收费系统来打比方,key是车牌号,value是进入时间,出来时摄像头再扫一次得到出库时间,进而得到每个车的入库时间,得到收费信息;key-value模型还可用于检查单词错误,将比如将几万个单词放进搜索树,可以保证很快就找到单词,在树均匀的情况下logN次就能实现
3.搜索二叉树实现
key模型和key-value模型就是搜索二叉树的壳子,重点是搜索二叉树的逻辑
a.结点的创建
使用struct是因为要在接下来的class BSTree里面频繁地访问BSTreeNode里面的成员,直接使用struct比class里加public好很多
b.find
搜索树中搜索是很重要的功能,根据左小右大的原则,我们可以很快写出相关代码,注意考虑空树,如果逻辑不完善,要补上。
当然在这里如果_root是空树,那就不会进入循环,直接就返回nullptr了,逻辑是严密的。
c.insert
insert是搜索二叉树核心之一,逻辑相对来讲就要复杂一些
首先insert要搞清楚什么时候要插入,什么时候不能插入。当数据重复时不能插入,当为空树时一定能插入。思考的顺序为:规则+空树(注意空树和根是贯穿整个二叉树的特殊情况)
这段代码还算简单
接下来就要分析一般情况了
首先我们要找到该插入的位置的父节点,用一个指针太麻烦了,我们需要一个指针在前面探路,用find类似的代码去找这个结点,另一个结点就是这个探路指针对应的父节点,当探路的走到空之后,另一个就指向了正确的父节点。
前面我们已经考虑了空树的情况了,这里我们的逻辑一定是严密的。
d.erase
erase的逻辑就有点复杂了,一步一步来,我们首先就能判断,没有的结点不能删,空树不能删,根可以删但是有特殊情况(规则+空树+根),我们发现空树就等于没有任何结点,可以一并处理。
我们先记住删根是一种特殊情况,先搞明白一般情况怎么处理。
一般情况分三种:删掉的结点没有孩子,有一个孩子,有两个孩子。
当没有孩子的时候,就直接让父节点指向空,delete掉结点就是了。如果只有一个结点,就让被删除的结点的父节点指向被删除节点的孩子就行了。我们发现,前两种的处理可以合并。
首先我们要找到想要被删除的结点
之后对被删除的进行处理即可
那删有两个孩子的节点呢?
处理方法是让这个结点左子树的最大结点或右子树的最小结点替换它,再删掉这个最大或者最小的结点。
首先我们要找到想要被删除的结点,和上面的代码是一样的。
之后我们去找左子树的最大节点,这个寻找操作和find逻辑很像,我们同样使用两个指针,一个探路,一个记录父节点。
找到之后需要把找到的那个最右边的值赋给想要删除的值
接下来要考虑的就是删除和修改指针的问题了,删除很简单,但是要仔细想想nodeP和nodeC的位置关系,nodeC一定是在nodeP的右边吗?如果左子树就一个结点,那么nodeC就在nodeP的左边,所以需要判断
注意思考的逻辑就是举例,对于二叉树而言,要验证它的逻辑是否严密,一般以空、一个节点、两个结点来考虑,如果这三种情况都通过,那一般是没有问题的。
前面我们说过删除根还没有处理。当根的左右都有结点时,逻辑是严密的,但如果根的左边或右边没有结点了,怎么办?
那属于一个孩子的情况
parent一直为空,会出现越界访问,所以要处理掉。
当parent == nullptr,我们就让根移位,在把原来根对应的结点删了,这样就不会出现那样的毛病了。
4.所有代码
包括上述代码,key-value对应代码,以及中序,析构函数等
#pragma once
#include <iostream>
#include <string>
#include <vector>
using namespace std;
namespace BSTree_Key
{
template<class T>
struct BSTreeNode
{
BSTreeNode(const T& val = T())
:_val(val)
,_left(nullptr)
,_right(nullptr)
{}
T _val;
BSTreeNode* _left;
BSTreeNode* _right;
};
template<class T>
class BSTree
{
typedef BSTreeNode<T> Node;
public:
~BSTree()
{
Destroy(_root);
}
Node* find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_val == val)
return cur;
if (cur->_val < val)
cur = cur->_right;
else if (cur->_val > val)
cur = cur->_left;
}
return nullptr;
}
bool insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
if (find(val) != nullptr)
return false;
Node* parent = nullptr,* cur = _root;
while (cur)
{
if (cur->_val < val)
parent = cur, cur = cur->_right;
else
parent = cur, cur = cur->_left;
}
if (parent->_val > val)
parent->_left = new Node(val);
else
parent->_right = new Node(val);
return true;
}
bool erase(const T& val)
{
if (find(val) == nullptr)
return false;
Node* parent = nullptr, *cur = _root;
while (cur->_val != val)
{
if (cur->_val < val)
parent = cur, cur = cur->_right;
else
parent = cur, cur = cur->_left;
}
if (cur->_right == nullptr)
{
if (parent == nullptr)
_root = cur->_left;
else if (parent->_right == cur)
parent->_right = cur->_left;
else
parent->_left = cur->_left;
delete cur;
}
else if (cur->_left == nullptr)
{
if (parent == nullptr)
_root = cur->_right;
else if (parent->_right == cur)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
delete cur;
}
else
{
Node* nodeP = cur, *nodeC = cur->_left;
while (nodeC->_right)
nodeP = nodeC, nodeC = nodeC->_right;
cur->_val = nodeC->_val;
if (nodeP->_right == nodeC)
nodeP->_right = nodeC->_left, delete nodeC;
else
nodeP->_left = nodeC->_left, delete nodeC;
}
return true;
}
void InOrder()
{
_InOrder(_root);
}
private:
void Destroy(Node* cur)
{
if (cur == nullptr)
return;
Destroy(cur->_left);
Destroy(cur->_right);
delete cur;
}
void _InOrder(Node* cur)
{
if (cur == nullptr)
return;
_InOrder(cur->_left);
cout << cur->_val << " ";
_InOrder(cur->_right);
}
private:
Node* _root = nullptr;
};
}
namespace BSTree_Key_Value
{
template<class K, class V>
struct BSTreeNode
{
BSTreeNode(const K& key, const V& value)
:_key(key)
,_value(value)
,_left(nullptr)
,_right(nullptr)
{}
K _key;
V _value;
BSTreeNode* _left;
BSTreeNode* _right;
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
~BSTree()
{
Destroy(_root);
}
Node* 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 cur;
}
return nullptr;
}
bool insert(const K& key, const V& value)
{
if (find(key) != nullptr)
return false;
Node* parent = nullptr, * cur = _root;
if (cur == nullptr)
{
_root = new Node(key, value);
return true;
}
while (cur)
{
if (cur->_key < key)
parent = cur, cur = cur->_right;
else
parent = cur, cur = cur->_left;
}
if (parent->_key < key)
parent->_right = new Node(key, value);
else
parent->_left = new Node(key, value);
return true;
}
bool erase(const K& key)
{
if (find(key) == nullptr)
return false;
Node* parent = nullptr, * cur = _root;
while (cur->_key != key)
{
if (cur->_key < key)
parent = cur, cur = cur->_right;
else
parent = cur, cur = cur->_left;
}
if (cur->_left == nullptr)
{
if (parent == nullptr)
_root = cur->_right, delete cur;
else if (parent->_left == cur)
parent->_left = cur->_right, delete cur;
else
parent->_right = cur->_right, delete cur;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
_root = cur->_left, delete cur;
else if (parent->_left == cur)
parent->_left = cur->_left, delete cur;
else
parent->_right = cur->_left, delete cur;
}
else
{
Node* nodeP = cur, * nodeC = cur->_left;
while (nodeC->_right)
nodeP = nodeC, nodeC = nodeC->_right;
cur->_key = nodeC->_key, cur->_value = nodeC->_value;
if (nodeP->_left == nodeC)
nodeP->_left = nodeC->_left;
else
nodeP->_right = nodeC->_left;
delete nodeC;
}
return true;
}
void InOrder()
{
_InOrder(_root);
}
private:
void Destroy(Node* cur)
{
if (cur == nullptr)
return;
Destroy(cur->_left);
Destroy(cur->_right);
delete cur;
}
void _InOrder(Node* cur)
{
if (cur == nullptr)
return;
_InOrder(cur->_left);
cout << cur->_key << " : " << cur->_value << endl;
_InOrder(cur->_right);
}
private:
Node* _root;
};
}