搜索二叉树
本章思维导图:
注:本章思维导图对应的
.xmind
和·.png
文件都已同步导入至资源,可供免费查阅
1. 基本特点
下图就是一棵经典的搜索二叉树:
我们规定,例如上图的搜索二叉树中,“48, 25”等这些数字称为每个节点的键值(key),键值用于确定每个节点在搜索二叉树的位置
可以发现,搜索二叉树具有这样的特点:
- 左子树每个节点的键值都比根小;右子树每个节点的键值都比根大
- 如果对搜索二叉树进行中序遍历,那么得到的键值排列是一个升序序列。例如上图:
10 25 34 48 61 73 81
- 由于键值确定了每个节点在树中的位置,因此键值是唯一的,不能重复
2. 两种模式
搜索二叉树基于节点的存储方式可以分为两种不同的模式:**基于键(key)的搜索二叉树和基于键值对(key_value)**的搜索二叉树
2.1 基于键(key)的搜索二叉树
这种结构的搜索二叉树的节点只存储该节点的键值(key),而不存储与键值相关的值value。其结构大致如下:
template<class K>
struct BSTreeNode
{
typedef BSTreeNode<K> Node;
Node* _left; //左孩子
Node* _right; //右孩子
K _key; //键值
//构造
BSTreeNode(const K& key)
: _left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
由于该结构的节点只存储了用于确定位置的键值(key),因此通常用于搜索二叉树节点的查找、删除、插入,而不用于修改操作
现实生活中的应用:
居民小区的汽车门禁系统就可以使用基于键(key)的搜索二叉树
- 小区内住户的汽车车牌号(key)会被录入小区的门禁系统
- 当外来车辆要进入时,系统会查找该汽车的车牌号是否被录入了系统,如果没有,就不能放行
- 同样,当车辆要驶出时,系统同样会查找该汽车的车牌号是否被录入了系统,如果没有,就不能放行
2.2 基于键值对(key_value)的搜索二叉树
这种结构的搜索二叉树的节点不仅存储了该节点的键值(key),也存储了与键值相关联的value值。其结构大致如下:
template<class K, class V>
struct BSTreeNode
{
typedef BSTreeNode<K, V> Node;
Node* _left; //左孩子
Node* _right; //右孩子
K _key; //键值
V _value; //value值
//构造
BSTreeNode(const K& key, const V& value)
: _left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
由于这种结构的节点存储了与键值(key)相关联的值,因此既可以进行节点的查找,插入,删除操作,也可以对键值(key)对应的value进行修改
现实生活中的应用:
商场的停车场就可以使用基于键值对(key_value)的搜索二叉树
- 当汽车要驶入商场的停车场时,系统会记录汽车的车牌号(key)以及进入时间(value)
- 当汽车要驶出商场的停车场时,系统会查找汽车的车牌号(key),通过key来找到进入的时间(value),从而计算出停车费
3. 基本操作
我们以基于键(key)的搜索二叉树为例,来学习搜索二叉树的查找节点、新增节点、删除节点的操作:
3.1 查
查找键值为key的节点
基于搜索二叉树“左子树每个节点的键值都比根小;右子树每个节点的键值都比根大”的特性我么可以很容易的对一个节点进行查找:
- 如果查找到节点的键值比根的键值大,那么就去跟得右子树找
- 如果查找到节点的键值比根的键值小,那么就去跟得左子树找
例如:
非递归版本:
//找到了就返回真true
//没找到就返回假false
bool find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
递归版本:
bool _findR(Node* root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key > key)
return _findR(root->_left, key);
else if (root->_key < key)
return _findR(root->_right, key);
else
return true;
}
/*
也可以写成:
bool _findR(Node* root, const K& key)
{
if (root == nullptr)
return false;
return root->_key == key || _findR(root->_left, key) || _findR(root->_right, key);
}
*/
3.2 增
新增节点,该节点的键值为key
3.2.1 非递归
要新增节点,首先就要找到新增节点的位置。这和查找节点的步骤类似:
- 当走到空
nullptr
时,该位置就是新增节点的位置 - 但需要注意,如果在查找的过程中碰到已有节点的键值等于key的情况,就不能继续插入了(键值唯一)
Node* cur = _root;
while (cur)
{
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false;
}
但是,这只是找到了新增节点插入的位置,我们还需要建立父亲和孩子的连接因此在找插入位置cur
的同时也需要保存cur
的父亲parent
。完整的代码为:
bool insert(const K& key)
{
Node* newNode = new Node(key);
if (_root == nullptr)
{
_root = newNode;
return true;
}
Node* parent = _root; //保存插入位置的父亲
Node* cur = _root; //寻找插入位置
while (cur)
{
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false;
}
//如果key大于父亲,就插在父亲的右边
//否则插在父亲的左边
if (key > parent->_key)
parent->_right = newNode;
else
parent->_left = newNode;
return true;
}
3.2.2 递归
递归版本同样也面临着新增节点和父亲的连接问题。
解决办法之一就是向函数的形参中加一个参数,用于保存父节点,但其实还有另一种更巧妙的方法:
我们可以利用引用,将待插入的nullptr
直接变为新增的节点,这样就可以省去连接的步骤了:
bool _insertR(Node*& root, const K& key)
{
if (root == nullptr)
{
Node* newNode = new Node(key);
root = newNode;
return true;
}
if (root->_key > key)
return _insertR(root->_left, key);
else if (root->_key < key)
return _insertR(root->_right, key);
else
return false;
}
3.3 删
删除键值为key的节点
3.3.1 非递归
第一步肯定是要先找到要被删除的节点,同时为了保证删除后搜索二叉树结构的正确性和完整性,肯定需要保存该节点的父亲来连接这个节点的左右孩子
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
//开始插入
}
}
根据被删除节点的左右孩子可以分为三种情况:
情况一——被删除的节点没有左右孩子:
则,如果被删除的节点为父亲的左孩子,则让父亲的左孩子连接到被删除节点的任意一个nullptr
即可;被删除节点为父亲的右孩子同理
情况二——被删除的节点有一个孩子:
此时,我们可以将被删除节点的孩子托管给父亲。具体分析如下:
如果被删除节点为父亲的右孩子,那么:
- 如果被删除节点的左孩子不为空,就将被删除节点的左孩子连接到父亲的右孩子
- 如果被删除节点的右孩子不为空,就将被删除节点的右孩子连接到父亲的右孩子
如果被删除节点为父亲的左孩子,那么:
- 如果被删除节点的左孩子不为空,就将被删除节点的左孩子连接到父亲的左孩子
- 如果被删除节点的右孩子不为空,就将被删除节点的右孩子连接到父亲的左孩子
情况三——被删除的节点左右孩子都有:
此时就不能将左右孩子托管给父亲了,因为父亲可能连接了其他节点
因此,我们需要采取一种新方法——替换法:
我们需要在被删除节点的左子树或右子树找到一个节点,并替换被删除的节点。
该节点满足的条件是:键值大于左子树的所有节点,小于右子树的所有节点。
因此,有两个满足该条件的节点:左子树最右边的节点(即左子树的最大节点);右子树最左边的节点(即右子树的最小节点)
实现代码:
bool erase(const K& key)
{
assert(_root);
Node* parent = _root;
Node* cur = _root;
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 (cur == parent)
_root = cur->_right;
else if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
//如果被删除节点的右子树为空
else if (cur->_right == nullptr)
{
if (cur == parent)
_root = cur->_left;
else if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
//如果被删除的节点左右子树都存在
else
{
//找到右子树的最小节点和最小节点的父亲
Node* rightMin = cur->_right;
Node* rightParent = cur;
while (rightMin->_left)
{
rightParent = rightMin;
rightMin = rightMin->_left;
}
//将被删除节点的键值key替换为右子树的最小节点的键值
cur->_key = rightMin->_key;
//保持右子树的最小节点的子树的连接
if (rightMin == rightParent->_right)
rightParent->_right = rightMin->_right;
else
rightParent->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
3.3.2 递归
这里为了处理好被删除节点的子树和被删除节点的父亲的连接关系,同样需要用到引用:
bool _eraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key > key)
return _eraseR(root->_left, key);
else if (root->_key < key)
return _eraseR(root->_right, key);
else
{
//记录要被删除的节点
Node* del = root;
if (root->_left == nullptr)
root = root->_right;
else if (root->_right == nullptr)
root = root->_left;
else
{
//找到右子树的最小节点
Node* rightMin = root->_right;
while (rightMin->_left)
rightMin = rightMin->_left;
//交换被删除节点和右子树最小节点的键值
swap(root->_key, rightMin->_key);
//执行到这一步,要被删除的键值为key的节点一定不会有两棵子树
//继续递归到右子树即可
return _eraseR(root->_right, key);
}
delete del;
return true;
}
}
4. 基于键(key)的搜索二叉树的实现代码
namespace key
{
template<class K>
struct BSTreeNode
{
typedef BSTreeNode<K> Node;
Node* _left;
Node* _right;
K _key;
BSTreeNode(const K& key)
: _left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
template<class K>
class BSTree
{
public:
typedef BSTreeNode<K> Node;
BSTree() = default; //强制生成构造
BSTree(const BSTree<K>& tree)
{
_root = copy(tree._root);
}
BSTree<K>& operator= (BSTree<K> tree)
{
swap(_root, tree._root);
return *this;
}
~BSTree()
{
destory(_root);
}
void inorder()
{
_inorder(_root);
cout << endl;
}
bool find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
bool insert(const K& key)
{
Node* newNode = new Node(key);
if (_root == nullptr)
{
_root = newNode;
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false;
}
if (key > parent->_key)
parent->_right = newNode;
else
parent->_left = newNode;
return true;
}
bool erase(const K& key)
{
assert(_root);
Node* parent = _root;
Node* cur = _root;
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 (cur == parent)
_root = cur->_right;
else if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (cur == parent)
_root = cur->_left;
else if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else
{
Node* rightMin = cur->_right;
Node* rightParent = cur;
while (rightMin->_left)
{
rightParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMin == rightParent->_right)
rightParent->_right = rightMin->_right;
else
rightParent->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
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);
}
private:
Node* _root = nullptr;
Node* copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newNode = new Node(root->_key);
newNode->_left = copy(root->_left);
newNode->_right = copy(root->_right);
return newNode;
}
void destory(Node* root)
{
if (root == nullptr)
return;
destory(root->_left);
destory(root->_right);
delete root;
}
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;
return root->_key == key || _findR(root->_left, key) || _findR(root->_right, key);
}
bool _insertR(Node*& root, const K& key)
{
if (root == nullptr)
{
Node* newNode = new Node(key);
root = newNode;
return true;
}
if (root->_key > key)
return _insertR(root->_left, key);
else if (root->_key < key)
return _insertR(root->_right, key);
else
return false;
}
bool _eraseR(Node*& root, const K& key)
{
if (root == nullptr)
return false;
if (root->_key > key)
return _eraseR(root->_left, key);
else if (root->_key < key)
return _eraseR(root->_right, key);
else
{
Node* del = root;
if (root->_left == nullptr)
root = root->_right;
else if (root->_right == nullptr)
root = root->_left;
else
{
Node* rightMin = root->_right;
while (rightMin->_left)
rightMin = rightMin->_left;
swap(root->_key, rightMin->_key);
return _eraseR(root->_right, key);
}
delete del;
return true;
}
}
};
}
5. 基于键值对(key_value)的搜索二叉树
key_value搜索二叉树的实现和key搜索二叉树的实现基本相同,只有查找结点和新增节点这两个操作有所不同,故下面只展示这两部分:
namespace key_value
{
template<class K, class V>
struct BSTreeNode
{
typedef BSTreeNode<K, V> Node;
Node* _left;
Node* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
: _left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
template<class K, class V>
class BSTree
{
public:
typedef BSTreeNode<K, V> Node;
BSTree() = default;
BSTree(const BSTree<K, V>& tree)
{
_root = copy(tree._root);
}
BSTree<K, V>& operator= (BSTree<K, V> tree)
{
swap(_root, tree._root);
return *this;
}
~BSTree()
{
destory(_root);
}
void inorder()
{
_inorder(_root);
cout << endl;
}
Node* find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool insert(const K& key, const V& value)
{
Node* newNode = new Node(key, value);
if (_root == nullptr)
{
_root = newNode;
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false;
}
if (key > parent->_key)
parent->_right = newNode;
else
parent->_left = newNode;
return true;
}
bool erase(const K& key)
{
assert(_root);
Node* parent = _root;
Node* cur = _root;
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 (cur == parent)
_root = cur->_right;
else if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (cur == parent)
_root = cur->_left;
else if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
delete cur;
return true;
}
else
{
Node* rightMin = cur->_right;
Node* rightParent = cur;
while (rightMin->_left)
{
rightParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMin == rightParent->_right)
rightParent->_right = rightMin->_right;
else
rightParent->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
Node* findR(const K& key)
{
return _findR(_root, key);
}
private:
Node* _root = nullptr;
Node* _findR(Node* root, const K& key)
{
if (root == nullptr)
return nullptr;
Node* ret = nullptr;
if (root->_key > key)
ret = _findR(root->_left, key);
else if (ret == nullptr && root->_key < key)
ret = _findR(root->_right, key);
else
ret = root;
return ret;
}
Node* copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newNode = new Node(root->_key);
newNode->_left = copy(root->_left);
newNode->_right = copy(root->_right);
return newNode;
}
void destory(Node* root)
{
if (root == nullptr)
return;
destory(root->_left);
destory(root->_right);
delete root;
}
void _inorder(Node* root)
{
if (root == nullptr)
return;
_inorder(root->_left);
cout << root->_key << ' ';
_inorder(root->_right);
}
};
}