多态讲完了,我们来讲点轻松的(也许)。
我们之前讲过二叉树,而二叉树中,又有一种特殊的树称之为二叉搜索树。
一、二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
例如下图,左边的是一颗二叉搜索树,右边不是:
为什么这样的树称作二叉搜索树呢?因为这树天生就十分适合搜索。我们可以这么想使用左边的树,如果我们想找到22的话,首先22比25小,所以一定在左子树,而22大于20,所以一定在右子树。然后我们就可以找到了。我们可以发现,这种树的效率天生就很高,这棵树最多找高度次。也就是说最多找N次(注意不是logN次,因为我们可能碰到下图中右边的树),下图中的两棵树都是二叉搜素树。也就是说时间复杂度为O(N)
普通的二叉搜索树的时间复杂度为O(N),但是我们可以对其进行一些特殊变换,使之成为AVL树,红黑树之类的(之后我们再讲)。这样时间复杂度就可以降为了O(logN)了。
这个树还有另一个名字是二叉排序树,这是因为当我们对这棵树进行中序遍历的时候,结果是升序的。
二、二叉搜索树的实现
1.二叉树的结点定义
对于二叉树的结点,我们类似于list的结点定义一样,使用一个struct类。
cpp
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& val = K())
:_key(val)
,_left(nullptr)
,_right(nullptr)
{}
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
};
2.二叉搜索树的结构
为了方便我们后续的结点定义,我们将结点给typedef一下。然后我们的成员变量其实也就这一个根节点。只要有根节点就可以构建出二叉搜索树了。
cpp
private:
typedef BSTreeNode<K> Node;
Node* _root;
3.二叉搜索树的构造函数
cpp
public:
BSTree()
:_root(nullptr)
{}
如上代码所示,对于构造函数其实是比较简答的,我们只需要将二叉树的根结点初始化为空即可
4.二叉搜索树的插入
对于插入接口,我们需要考虑的情况有如下两种:
- 二叉树为空的时候,直接new一个结点插入上去即可
- 如果二叉树不为空,那么我们就需要进行比较
例如上面的例子,我们此时插入一个16
16小于25,向左走。
16小于20,向左走。
16大于10,向右走,并插入空
注意:如果恰好等于当前结点的值,那么我们可以认为插入失败了,因为此时已经不满足二叉搜索树的定义了。
代码实现:
我们现在想的方法是对二叉树进行迭代,迭代到空new一个新节点插入,但是如果我们只是对二叉树进行迭代最终我们会发现如此迭代下去,最终的当前结点一定会为空指针,这并非我们所期望的。我们肯定是还需要它的父节点的,这样才能让我目标值插入进去。所以我们还需要一个值来记录当前所迭代位置的父节点。即我们下面写的parent。
有了parent以后,我们现在就是new一个新的结点,然后直接parent链接起来。不过此时又分为两种情形,是左孩子还是右孩子,其实并不好确定。于是我们可以使用parent当前的值与目标值进行比对从而判断出是插入左孩子还是右孩子。
cpp
public:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else
{
Node* parent = _root;
Node* cur = _root;
while (cur != nullptr)
{
if (cur->_key == key)
{
return false;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
cur = new Node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else if (parent->_key < key)
{
parent->_right = cur;
}
return true;
}
}
5.二叉搜索树的中序遍历
插入了以后,我们可以使用中序遍历对其进行输出,由于中序遍历就是排序后的情形,所以我们可以很方便的看到我们当前二叉树的结果。
对于排序,我们最好写一个子函数来实现,因为没有子函数的话,参数不好进行控制,会使得代码十分繁琐。
如下代码所示,对于中序遍历我们其实还是比较容易看懂的。无非就是先左子树,然后根,然后右子树即可。我们最好将子函数放在私有的部分中,因为在类外我们并不需要直接使用这个函数
cpp
public:
void InOrder()
{
_InOrder(_root);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
6.二叉搜索树的查找
对于查找的逻辑,是非常简单的,它与插入也是比较相似的,当我们当前结点的值小于目标的时候,迭代到右子树,当大于目标的时候,迭代到左子树。当相等的时候返回true即可。但若当cur都为nullptr了的时候还没有找到,那就是没有找到,返回false即可
cpp
public:
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key == key)
{
return true;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
}
return false;
}
7.二叉搜索树的删除
对于二叉搜索树的删除操作,其实是比较困难的。
首先查找元素是否在二叉搜索树中,如果不存在就返回false
如果存在,那么此时要删除的节点可能分为以下四种情况:
- 要删除的节点无子节点
- 要删除的节点只有左子节点
- 要删除的节点只有右子节点
- 要删除的节点有左、右子节点
第一种情况是最好处理的,释放节点,直接把父节点指向改为空。
第二种情况,要删除的节点只有左子节点,我们还是用上面的例子,此时18就是一个只有左子节点的节点
如果我们要删除2,只需要将2的父节点 和2的左子节点链接,然后直接删除2即可。
第三种情况,要删除的节点只有右子节点,此时13就是一个只有右子节点的节点
如果我们要删除13,只需要将13的父节点 和13的右子节点链接,然后直接删除13即可
总结,第二种和第三种情况的删除方式其实也适用于第一种情况,因为无子节点,也就是左右都为空,此时父节点链接的是一个空指针,并删除目标节点即可。
第四种:要删除的节点有左、右节点,此时23符合要求
我们的目标是,在23的子树中寻找一个值,将23与这个值交换。这种方法叫做替换法。
要寻找的值必须满足交换后二叉搜索树的性质不变,所以我们需要在23的左子树找出一个最大值,或者在23的右子树找出一个最小值。
前面提到过对二叉搜索树中序遍历可以得到一个有序的序列,所以23的子树的最大值,也就是在左子树中做一次中序遍历下的最后一个数,也就是21。简单来说,我们只需要从23向左走一步,然后一直向右走,就可以找到左子树的最大值了
而23的右子树的最小值,也就是在右子树中做一次中序遍历下的第一个数,也就是26。和上面类似的,我们只需要从26向右走一步,然后一直向左走,就可以找到右子树的最小值了。
因此,我们可以将23和21替换,或者将23和26替换
这里就只展示23和21替换的情况,当学会规律后,另一种情况也就学会了。
替换后,还需要把23的父节点和23的子节点链接,这里又有两种情况需要区分,否则导致错误:
- 替换后23的父节点位于23原先的位置
- 替换后23的父节点不位于23原先的位置
可以看出,在这种情况下23的父节点位于23原先的位置,那么就要将此时23的左子节点赋值给父节点的left,然后就可以直接删除23了
我们改造一下这颗二叉搜索树.使父节点不位于原先的位置(另一种情况)
假设我们要删去12(改造后12的左子树的最大值为11)
此时,就需要将12的左子节点赋值给父节点的right,然后直接删除12。
这里是一个易错点,需要注意情况的判断。以上是对二叉搜索树的操作部分。
因此我们引出了两种写法,
以下是先考虑parent与cur这两代人之间的关系所写的代码
cpp
public:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
if (parent == nullptr)
{
if (cur->_left == nullptr)
{
_root = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
_root = cur->_left;
delete cur;
return true;
}
else
{
Node* leftMaxParent = cur;
Node* leftMax = cur->_left;
if (leftMax->_right == nullptr)
{
leftMax->_right = cur->_right;
delete cur;
_root = leftMax;
return true;
}
while (leftMax->_right)
{
leftMaxParent = leftMax;
leftMax = leftMax->_right;
}
std::swap(leftMax->_key, cur->_key);
leftMaxParent->_right = leftMax->_left;
delete leftMax;
leftMax = nullptr;
return true;
}
}
if (parent->_left == cur)
{
if (cur->_left == nullptr)
{
parent->_left = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
parent->_left = cur->_left;
delete cur;
return true;
}
else
{
Node* leftMaxParent = cur;
Node* leftMax = cur->_left;
if (leftMax->_right == nullptr)
{
leftMax->_right = cur->_right;
delete cur;
parent->_left = leftMax;
return true;
}
while (leftMax->_right)
{
leftMaxParent = leftMax;
leftMax = leftMax->_right;
}
std::swap(leftMax->_key, cur->_key);
leftMaxParent->_right = leftMax->_left;
delete leftMax;
leftMax = nullptr;
return true;
}
}
else
{
if (cur->_left == nullptr)
{
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
parent->_right = cur->_left;
delete cur;
return true;
}
else
{
Node* leftMaxParent = cur;
Node* leftMax = cur->_left;
if (leftMax->_right == nullptr)
{
leftMax->_right = cur->_right;
delete cur;
parent->_right = leftMax;
return true;
}
while (leftMax->_right)
{
leftMaxParent = leftMax;
leftMax = leftMax->_right;
}
std::swap(leftMax->_key, cur->_key);
leftMaxParent->_right = leftMax->_left;
delete leftMax;
leftMax = nullptr;
return true;
}
}
}
}
return false;
}
以下是根据先考虑cur与其孩子之间的关系所写的代码
cpp
public:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
if (cur->_left == nullptr)
{
if (parent == nullptr)
{
_root = cur->_right;
}
else if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else if (parent->_right == cur)
{
parent->_right = cur->_right;
}
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left;
}
else if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else if (parent->_right = cur)
{
parent->_right = cur->_left;
}
}
else
{
Node* leftMax = cur->_left;
Node* leftMaxParent = cur;
while (leftMax->_right)
{
leftMaxParent = leftMax;
leftMax = leftMax->_right;
}
std::swap(cur->_key, leftMax->_key);
if (leftMaxParent->_left == leftMax)
{
leftMaxParent->_left = leftMax->_left;
}
else
{
leftMaxParent->_right = leftMax->_left;
}
cur = leftMax;
}
delete cur;
return true;
}
}
}
这两种方法都可以完成这个接口,但是代码量有显著的差异。这里推荐先考虑cur和其后代之间的关系。因为替代法这个出现于cur与其后代之间的关系之中。
8.二叉搜索树的查找(递归)
前面,都是讲的非递归,此处为补充(能用迭代还是用迭代去找)
二叉树其实本身还是比较适合递归的。为了可以控制参数,我们要写一个子函数去解决,当根节点为空的时候,直接返回false即可,当前的结点小于要查找的结点,那么去右子树找,当大于时候,去左子树找即可。
cpp
public:
bool FindR(const K& key)
{
return _FindR(_root, key);
}
private:
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key == key)
{
return true;
}
else if (root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return _FindR(root->_right, key);
}
}
9.二叉搜索树的插入(递归)
大体思路同上,也是补充而已
cpp
public:
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
private:
bool _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;
}
}
10.二叉搜索树的删除(递归)
这个写的挺妙的
cpp
public:
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
private:
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* leftMax = root->_left;
while (leftMax->_right)
{
leftMax = leftMax->_right;
}
std::swap(leftMax->_key, root->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
11.二叉搜索树的销毁
拉欧i据 直接按照后序的方式进行销毁即可。
cpp
public:
~BSTree()
{
_Destory(_root);
}
private:
void _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
root == nullptr;
}
这里最好传引用,因为这样可以置空root指针
12.拷贝构造函数
拷贝构造函数,我们需要实现一个深拷贝,和之前的二叉树基本一样,我们讲deque时也做过了。
这里我们使用先序的方式进行构造:
cpp
public:
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
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;
}
13.运算符重载
这里主要用的还是赋值运算符重载吧,
现代写法启动:
cpp
public:
BSTree<K>& operator=(BSTree<K> t)
{
std::swap(_root, t._root);
return *this;
}
14.二叉搜索树的实现
cpp
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
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* prev = nullptr;
Node* cur = _root;
while (cur)
{
prev = cur;
if (key > cur->_key)
cur = cur->_right;
else if (key < cur->_key)
cur = cur->_left;
else
return false;
}
cur = new Node(key);
if (key > prev->_key)
prev->_right = cur;
else
prev->_left = cur;
return true;
}
bool insertR(const K& key) //递归版本
{
return _insertR(_root, key);
}
bool 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 true;
}
return false;
}
bool findR(const K& key) //递归版本
{
return _findR(_root, key);
}
bool erase(const K& key) //替换法:把目标替换成左子树的最大值或右子树的最小值
{
Node* parent = nullptr;
Node* cur = _root;
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 (cur == parent->_right)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
}
else if(cur->_right == nullptr) //右子树为空
{
if (cur == _root)
_root = cur->_left;
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
}
else //左右子树都不为空
{
parent = cur;
Node* leftmid = cur->_left;
while (leftmid->_right)
{
parent = leftmid;
leftmid = leftmid->_right;
}
swap(cur->_key, leftmid->_key);
if (parent == cur)
parent->_left = leftmid->_left;
else
parent->_right = leftmid->_left;
cur = leftmid;
}
delete cur;
return true;
}
}
return false;
}
bool eraseR(const K& key) //递归版本
{
return _eraseR(_root, key);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
BSTree() = default;
BSTree(const BSTree<K>& t)
{
_root = copy(t._root);
}
~BSTree()
{
Destroy(_root);
}
private:
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 Destroy(Node*& root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
bool _findR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key == key)
return true;
if (key > root->_key)
return _findR(root->_right, key);
else
return _findR(root->_left, key);
}
bool _insertR(Node*& root, const K& key) //参数中指针一定要用引用,不然链接不上
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (key > root->_key)
return _insertR(root->_right, key);
else if (key < root->_key)
return _insertR(root->_left, key);
else
return false;
}
bool _eraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (key > root->_key)
return _eraseR(root->_right, key);
else if (key < root->_key)
return _eraseR(root->_left, key);
else
{
if (root->_left == nullptr)
{
Node* tmp = root;
root = root->_right; //由于引用,root是父节点子树的别名,所以可以直接修改
delete tmp;
}
else if (root->_right == nullptr)
{
Node* tmp = root;
root = root->_left;
delete tmp;
}
else
{
Node* leftmid = root->_left;
while (leftmid->_right)
leftmid = leftmid->_right;
swap(root->_key, leftmid->_key);
return _eraseR(root->_left, key);
}
return true;
}
}
private:
Node* _root = nullptr;
};
三、二叉搜索树的应用
1)key模型
即只有key作为关键码,只需要在二叉搜索树中存储一个key即可。例如我们将业主的信息作为key,将整个小区所有业主的key存储进二叉搜索树中,如果此时要查询一个人是否是小区的业主,只需要将他的key在二叉搜索树中查找一下就可以知道这个人是否是业主了。后面要学习的set,就是一个key模型
2)key/value模型
每一个关键码key,都有与之对应的值value,也就是要在二叉搜索树中存储<key, value>的键值对。例如字典就是一个key/value模型,英文单词作为key,中文作为value,通过key就可以查找到value。又例如哈希表或者类似的用于统计某个物品出现次数的结构,通过查找该物品就可以快速得到其出现次数,这也是一个key/value模型。后面要学习的map,就是一个key/value模型我们可以将二叉搜索树改造为key/value的结构,让节点中多存储一个value,再进行一些修改即可
cpp
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
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
{
typedef BSTreeNode<K, V> Node;
public:
bool insert(const K& key, const V& val)
{
if (_root == nullptr)
{
_root = new Node(key, val);
return true;
}
Node* prev = nullptr;
Node* cur = _root;
while (cur)
{
prev = cur;
if (key > cur->_key)
cur = cur->_right;
else if (key < cur->_key)
cur = cur->_left;
else
return false;
}
cur = new Node(key, val);
if (key > prev->_key)
prev->_right = cur;
else
prev->_left = cur;
return true;
}
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;
}
return nullptr;
}
bool erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
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 (cur == parent->_right)
parent->_right = cur->_right;
else
parent->_left = cur->_right;
}
}
else if (cur->_right == nullptr)
{
if (cur == _root)
_root = cur->_left;
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
}
else
{
parent = cur;
Node* leftmid = cur->_left;
while (leftmid->_right)
{
parent = leftmid;
leftmid = leftmid->_right;
}
swap(cur->_key, leftmid->_key);
if (parent == cur)
parent->_left = leftmid->_left;
else
parent->_right = leftmid->_left;
cur = leftmid;
}
delete cur;
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " : " << root->_val << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
本篇文章到此结束,如有错误感谢指正。