搜索二叉树
1. 什么是搜索二叉树?
搜索二叉树是一个特殊的二叉树,它的特点是:
若左子树不为空,则左子树所有节点小于根,
若右子树不为空,则右子树所有节点大于根。
正因如此,当我们以中序的方式遍历它,就能得到一个从小到大排序的序列
2. 模拟实现搜索二叉树
此次模拟实现,为KV模型,即通过key的值,可以找到value的值。听起来挺抽象,举个例子。
学校放假,该回家了,我在铁路12306上购买了回家的高铁票。在检票时,我们被要求刷身份证并且扫脸来检票。这就是典型的KV模型,身份证号码就是key值,通过这个key值,可以在系统中找到我的购票信息——value值,核对成功,即检票成功了。
我们来看看代码,看似很长,其实很简单。
#pragma once
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
// 实现K/V模型
template<class K, class V>
struct BSNode
{
BSNode(const K& key = K(), const V& value = V())
: _left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{ }
BSNode<K, V>* _left;
BSNode<K, V>* _right;
K _key;
V _value;
};
template<class K, class V>
class BSTree
{
public:
typedef BSNode<K, V> Node;
BSTree()
: _root(nullptr)
{}
Node* Find(const K& key)
{
// 查找值为key的节点
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
bool Insert(const K& key, const V& value)
{
// 1. 根为空
if (!_root)
{
_root = new Node(key, value);
}
// 2. 根不为空
// 疑问:参数有两个,是根据key来定位,还是根据value来定位呢?
// 假设先以key来定位吧!
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
// 应该插入到cur的左子树
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
// 应该插入到cur的右子树
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
// 搜索二叉树中存在_key值为key的节点,不再插入重复。
else
{
return false;
}
}
// 到这里,就已经找到了该插入的位置(cur为叶子节点的空孩子,注意要区分是叶子的左孩子还是右孩子)
if (key < parent->_key) // 说明cur是叶子的左孩子,新节点是叶子(parent)的左孩子
{
parent->_left = new Node(key, value);
}
else // 说明cur是叶子的右孩子,新节点是叶子(parent)的右孩子
{
parent->_right = new Node(key, value);
}
// 插入成功
return true;
}
bool Erase(const K& key)
{
if (!_root)
return false;
Node* cur = _root;
Node* parent = cur;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else // 找到了
{
// 1. 左孩子为空
if (!cur->_left)
{
// cur是根节点
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_left;
}
}
}
// 2. 右孩子为空
else if (!cur->_right)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
// 3. 左右孩子都不为空
else
{
// 找到右子树的最左节点
parent = cur;
Node* rightMax = cur->_right;
while (rightMax->_left)
{
parent = rightMax;
rightMax = rightMax->_left;
}
swap(cur->_key, rightMax->_key);
// 删除rightMax
if (rightMax == parent->_right)
{
parent->_right = rightMax->_right;
}
else
{
parent->_left = rightMax->_right;
}
cur = rightMax;
}
delete cur;
return true;
}
}
// 没找到
return false;
}
// 前序/中序/后序,这三者自身又分为循环/递归,一共写6种实现方法(实际上还有层序和morris遍历,应该有12种方法)
void InOrder()
{
// 1. 前序递归
cout << "前序递归:";
_InOrder_F_R(_root);
cout << endl;
// 2. 前序循环
cout << "前序循环:";
_InOrder_F_C();
cout << endl;
// 3. 中序递归
cout << "中序递归:";
_InOrder_M_R(_root);
cout << endl;
// 4. 中序循环
cout << "中序循环:";
_InOrder_M_C();
cout << endl;
// 5. 后序递归
cout << "后序递归:";
_InOrder_B_R(_root);
cout << endl;
// 6. 后序循环
cout << "后序循环:";
_InOrder_B_C();
cout << endl;
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool InsertR(const K& key, const V& value)
{
return _InsertR(_root, key, value);
}
private:
// 1. 前序递归
void _InOrder_F_R(Node* root)
{
if (!root)
return;
cout << root->_key << "->" << root->_value << " ";
_InOrder_F_R(root->_left);
_InOrder_F_R(root->_right);
}
// 2. 中序递归
void _InOrder_M_R(Node* root)
{
if (!root)
return;
_InOrder_M_R(root->_left);
cout << root->_key << "->" << root->_value << " ";
_InOrder_M_R(root->_right);
}
// 3. 后序递归
void _InOrder_B_R(Node* root)
{
if (!root)
return;
_InOrder_B_R(root->_left);
_InOrder_B_R(root->_right);
cout << root->_key << "->" << root->_value << " ";
}
// 4. 前序循环
void _InOrder_F_C()
{
stack<Node*> st;
vector<K> v_k;
vector<V> v_v;
Node* cur = _root;
// 只有当cur为空且栈里没有数据了,所有工作才完成
while (cur || !st.empty())
{
// 如果当前节点cur不为空,就把cur入栈
while (cur)
{
st.push(cur);
cur = cur->_left;
v_k.push_back(st.top()->_key);
v_v.push_back(st.top()->_value);
}
if (!cur)
cur = st.top();
// 此时左子节点们已经入栈完成了
st.pop();
cur = cur->_right;
}
for (size_t i = 0; i < v_v.size(); ++i)
{
cout << v_k[i] << "->" << v_v[i] << " ";
}
}
// 5. 中序循环
void _InOrder_M_C()
{
stack<Node*> st;
vector<K> v_k;
vector<V> v_v;
Node* cur = _root;
// 只有当cur为空且栈里没有数据了,所有工作才完成
while (cur || !st.empty())
{
// 如果当前节点cur不为空,就把cur入栈
while (cur)
{
st.push(cur);
cur = cur->_left;
}
if (!cur)
cur = st.top();
// 此时左子节点们已经入栈完成了
v_k.push_back(st.top()->_key);
v_v.push_back(st.top()->_value);
st.pop();
cur = cur->_right;
}
for (size_t i = 0; i < v_v.size(); ++i)
{
cout << v_k[i] << "->" << v_v[i] << " ";
}
}
// 6. 后序循环
void _InOrder_B_C()
{
stack<Node*> st;
vector<K> v_k;
vector<V> v_v;
Node* cur = _root;
Node* prev = nullptr;
// 只有当cur为空且栈里没有数据了,所有工作才完成
while (cur || !st.empty())
{
// 如果当前节点cur不为空,就把cur入栈
while (cur)
{
st.push(cur);
cur = cur->_left;
}
// 此时左子节点们已经入栈完成了
// 如果栈顶节点的右子树为空,或者上一个访问的就是栈顶节点的右子树,则说明可以访问根了
if (!st.top()->_right || st.top()->_right == prev)
{
prev = st.top();
v_k.push_back(st.top()->_key);
v_v.push_back(st.top()->_value);
st.pop();
}
else
{
cur = st.top()->_right;
}
}
for (size_t i = 0; i < v_v.size(); ++i)
{
cout << v_k[i] << "->" << v_v[i] << " ";
}
}
bool _InsertR(Node*& root, const K& key, const V& value)
{
if (!root)
{
root = new Node(key, value);
return true;
}
// 找位置
if (key < root->_key)
{
_InsertR(root->_left, key, value);
}
else if (key > root->_key)
{
_InsertR(root->_right, key, value);
}
else // 存在相等,这是搜索二叉树不允许的
{
return false;
}
}
bool _EraseR(Node*& root, const K& key)
{
// 如果root为空,说明没有找到
if (!root)
return false;
if (root->_key < key)
{
_EraseR(root->_right, key);
}
else if (root->_key > key)
{
_EraseR(root->_left, key);
}
else
{
Node* del = root;
// 找到了!
if (!root->_left) // 左孩子为空
{
root = root->_right;
}
else if (!root->_right) // 右孩子为空
{
root = root->_left;
}
else // 左右孩子都不为空
{
// 找到左子树的最右节点
Node* leftMax = root->_left;
while (leftMax->_right)
{
leftMax = leftMax->_right;
}
swap(root->_key, leftMax->_key);
return _EraseR(root->_left, key);
}
delete del;
return true;
}
}
private:
Node* _root;
};
3. 效率分析
搜索二叉树,它的的查找、排序、插入、删除的效率都很不错,时间复杂度一般为O(logN),因为它的遍历仅仅是高度次。
但是也有特例:当一个二叉树存在大量的类似链表的结构(也就是大量节点只有一个子节点),那么它的高度就会与N相近,此时的时间复杂度就是O(N)了。
也正因此,前人又在此基础上设计出了很多结构,来压缩搜索二叉树的高度,以避免出现这种特例,那便是AVL树、红黑树等等,这些结构较复杂,参见我后面的博客。