概念与特性
二叉搜索树(Binary Search Tree, BST),又称二叉排序树,是一种高效的数据存储与检索结构。它具有以下核心特性:
-
有序性:对于树中的任意节点:
- 左子树所有节点的值 ≤ 当前节点的值
- 右子树所有节点的值 ≥ 当前节点的值
-
递归结构:左右子树本身也是二叉搜索树
-
重复值处理:根据实现需求,可以允许或禁止重复值插入。在C++标准库中,
map
和set
不允许重复,而multimap
和multiset
允许。
性能分析
二叉搜索树的性能高度依赖于树的平衡程度:
- 最优情况(完全或接近完全二叉树):查找、插入、删除操作的时间复杂度为O(log₂N)
- 最差情况(退化为链表):时间复杂度恶化为O(N)
与二分查找相比,二叉搜索树有以下优势:
- 不需要连续存储空间
- 支持高效动态插入和删除
- 适用于内存中的数据组织
核心操作详解
插入操作
插入算法遵循以下步骤:
- 若树为空,创建新节点作为根节点
- 若树非空:
- 新值 < 当前节点值 → 向左子树递归
- 新值 > 当前节点值 → 向右子树递归
- 对于允许重复值的情况,需保持一致的插入方向(始终左或始终右)
bool Insert(const K& key) {
if (_root == nullptr) {
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
parent = cur;
if (key < cur->_key) {
cur = cur->_left;
} else if (key > cur->_key) {
cur = cur->_right;
} else {
return false; // 重复值处理
}
}
cur = new Node(key);
if (key < parent->_key) {
parent->_left = cur;
} else {
parent->_right = cur;
}
return true;
}
查找操作
查找过程从根节点开始:
- 目标值 < 当前节点 → 向左子树查找
- 目标值 > 当前节点 → 向右子树查找
- 找到匹配值或到达空节点终止
对于允许重复值的情况,通常需要返回中序遍历中的第一个匹配节点。如下图,查找3,要
找到1的右孩子的那个3返回
删除操作
删除操作分为四种情况处理:
- 叶子节点:直接删除
- 只有右子树:用右孩子替代被删节点
- 只有左子树:用左孩子替代被删节点
- 左右子树都存在:
- 找到右子树的最小节点(或左子树的最大节点)
- 交换两节点值
- 删除替代节点(此时必为情况1或2)
bool Erase(const K& key) {
// 查找过程...
// 找到节点后的处理
if (cur->_left == nullptr) {
// 处理右子树或叶子节点情况
} else if (cur->_right == nullptr) {
// 处理左子树情况
} else {
// 处理双子树情况 - 找右子树最小节点
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left) {
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key; // 值替换
// 删除rightMin节点(必为情况1或2)
}
}
应用场景
纯Key场景
适用于只需判断元素是否存在的场景:
- 场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位业主的车牌号录入后台系统,车辆进入时扫描车牌在不在系统中,在则抬杆,不在则提示非本小区车辆,无法进入。
- 场景2:检查一篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树,读取文章的单词,查找是否在二叉搜索树中,不在则波浪线标红提示。
Key-Value场景
适用于需要关联数据的场景:
- 场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和vlaue(中文),搜索时输入英文,同时查找到了英文对应的中文。
- 场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌和入场时间,出口离场时,扫描牌,查找入场时间,用当前时间减去入场时间计算出停车时长,计算出停车费用,缴费后抬杆,辆离场。
- 场景3:统计一篇文章中单词出现的次数,读取一个单词,查找单词是否存在,不存在这个说第一次出现,(单词,1),单词存在,则++单词对应的次数。
// Key-Value节点结构
template<class K, class V>
struct BSTNode {
K _key;
V _value;
BSTNode<K,V>* _left;
BSTNode<K,V>* _right;
BSTNode(const K& key, const V& value)
: _key(key), _value(value), _left(nullptr), _right(nullptr) {}
};
纯Key版本和KV版本二叉搜索树实现
#pragma once
#include <iostream>
using namespace std;
// 纯键(Key)版本的二叉搜索树
namespace K
{
// 二叉搜索树节点结构
template <class K>
struct BSTNode
{
K _key; // 节点存储的键值
BSTNode<K>* _left; // 左子节点指针
BSTNode<K>* _right; // 右子节点指针
// 构造函数
BSTNode(const K& key)
: _key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
// 二叉搜索树类
template <class K>
class BSTree
{
using Node = BSTNode<K>; // 类型别名,方便使用
public:
BSTree() : _root(nullptr) {} // 默认构造函数
// 插入键值
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key); // 树为空时直接创建根节点
return true;
}
Node* cur = _root;
Node* parent = nullptr;
// 查找插入位置
while (cur)
{
parent = cur;
if (key < cur->_key)
{
cur = cur->_left; // 向左子树查找
}
else if (key > cur->_key)
{
cur = cur->_right; // 向右子树查找
}
else
{
return false; // 键值已存在,插入失败
}
}
// 创建新节点并插入
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur; // 作为左子节点
}
else
{
parent->_right = cur; // 作为右子节点
}
return true;
}
// 查找键值是否存在
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return true; // 找到键值
}
}
return false; // 未找到键值
}
// 删除键值
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
// 查找要删除的节点
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
// 找到要删除的节点,分三种情况处理
if (cur->_left == nullptr) // 情况1:左子节点为空
{
if (cur == _root)
{
_root = cur->_right; // 删除的是根节点
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr) // 情况2:右子节点为空
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else // 情况3:左右子节点都不为空
{
// 找到右子树的最小节点(替代节点)
Node* replace = cur->_right;
Node* replace_parent = cur;
while (replace->_left)
{
replace_parent = replace;
replace = replace->_left;
}
// 用替代节点的值替换当前节点值
cur->_key = replace->_key;
// 删除替代节点
if (replace_parent->_left == replace)
{
replace_parent->_left = replace->_right;
}
else
{
replace_parent->_right = replace->_right;
}
delete replace;
}
return true; // 删除成功
}
}
return false; // 未找到要删除的键值
}
// 中序遍历打印树内容
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
private:
// 递归中序遍历辅助函数
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
std::cout << root->_key << " ";
_InOrder(root->_right);
}
Node* _root = nullptr; // 根节点指针
};
}
// 键值对(Key-Value)版本的二叉搜索树
namespace KV
{
// 键值对节点结构
template <class K, class V>
struct BSTNode
{
K _key; // 键
V _value; // 值
BSTNode<K,V>* _left; // 左子节点
BSTNode<K,V>* _right; // 右子节点
// 构造函数
BSTNode(const K& key, const V& value)
: _key(key)
, _value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
// 键值对二叉搜索树类
template <class K, class V>
class BSTree
{
using Node = BSTNode<K,V>; // 类型别名
public:
BSTree() = default; // 默认构造函数
// 拷贝构造函数
BSTree(const BSTree<K,V>& other)
{
_root = Copy(other._root);
}
// 赋值运算符重载(使用拷贝交换惯用法)
BSTree<K,V>& operator=(BSTree<K,V> other)
{
std::swap(_root, other._root);
return *this;
}
// 析构函数
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
// 插入键值对
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
// 查找插入位置
while (cur)
{
parent = cur;
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return false; // 键已存在,插入失败
}
}
// 创建新节点并插入
cur = new Node(key, value);
if (key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = 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* cur = _root;
Node* parent = nullptr;
// 查找要删除的节点
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) // 情况1:左子节点为空
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if (cur->_right == nullptr) // 情况2:右子节点为空
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else // 情况3:左右子节点都不为空
{
// 找到右子树的最小节点(替代节点)
Node* replace = cur->_right;
Node* replace_parent = cur;
while (replace->_left)
{
replace_parent = replace;
replace = replace->_left;
}
// 用替代节点的键值替换当前节点键值
cur->_key = replace->_key;
cur->_value = replace->_value;
// 删除替代节点
if (replace_parent->_left == replace)
{
replace_parent->_left = replace->_right;
}
else
{
replace_parent->_right = replace->_right;
}
delete replace;
}
return true; // 删除成功
}
}
return false; // 未找到要删除的键
}
// 中序遍历打印树内容
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
private:
// 递归中序遍历辅助函数
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
std::cout << root->_key << ":" << root->_value << std::endl;
_InOrder(root->_right);
}
// 递归销毁树辅助函数
void Destroy(Node* root)
{
if (root == nullptr) return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
// 递归拷贝树辅助函数
Node* Copy(Node* root)
{
if (root == nullptr) return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
Node* _root = nullptr; // 根节点指针
};
}