前言**:**二叉搜索树(Binary Search Tree, BST)是计算机科学中最基础且强大的数据结构之一,它凭借 O(log n) 的平均查找、插入和删除效率,成为高效数据管理的关键工具。在C++中,BST不仅是标准模板库(STL)std::set 和 std::map 的底层实现基础,更是理解更高级数据结构(如AVL树、红黑树)的必经之路。
目录

一、二叉搜索树介绍
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树

二、二叉搜索树的实现
这个模块,我们会从结构构建到增删查改,一步步演示二叉搜索树的搭建过程。
结构创建
实现一棵二叉搜索树,我们需要一个节点结构、一个功能结构
其中节点结构里面有左右子节点(left,right)、一个数据存储变量(date):
cpp
//节点结构
template<class T>
struct Tree_Node
{
Tree_Node(const T _date)
:left(nullptr)
,right(nullptr)
,date(_date)
{ }
Tree_Node<T>* left;
Tree_Node<T>* right;
T date;
};
功能结构用来实现二叉搜索树的功能:
cpp
//功能结构
template<class T>
class BST
{
typedef Tree_Node<T> Node;
public:
//构造
BST()
:node(nullptr)
{ }
//功能实现
private:
Node* node;
};
插入节点
插入操作的简述:
- 先从根节点开始搜索插入位置:
- 若目标值小于当前节点值,则向当前节点的左子树移动;若目标值大于当前节点值,则向当前节点的右子树移动
- 当到达叶子节点时,根据目标值与叶子节点值的大小关系,将新节点作为叶子节点的左子节点或右子节点插入
平均时间复杂度为 O(logn),最坏时间复杂度为O(n)
插入节点我们需要根据数据的大小来判断插在左右节点的 nullptr 位置,我们这里用循环来写更容易理解:
cpp
// 插入节点(非递归)
void Insert(const T& date) {
// 如果根节点为空
if (root == nullptr) {
root = new Node(date);
return;
}
// 根据数据大小查找插入位置
Node* parent = nullptr;
Node* cur = root;
while (cur) {
parent = cur; // 记录父节点
if (date < cur->date) {
cur = cur->left; // 向左子树查找
}
else if (date > cur->date) {
cur = cur->right; // 向右子树查找
}
else {
return; // 数据已存在,直接返回
}
}
// 创建新节点并插入
if (date < parent->date) {
parent->left = new Node(date); // 插入左侧
}
else {
parent->right = new Node(date); // 插入右侧
}
中序遍历
cpp
//中序遍历
void Inorder()
{
_Inorder(node);
}
记得私有中序遍历辅助函数
void _Inorder(Node* ptr)
{
//遇到空就返回
if (ptr == nullptr)
{
return;
}
_Inorder(ptr->left);
cout << ptr->date << " ";
_Inorder(ptr->right);
}

查找节点
cpp
// 查找节点(非递归)
bool Find(const T& date) {
Node* cur = root;
while (cur) {
if (date < cur->date) {
cur = cur->left; // 向左查找
}
else if (date > cur->date) {
cur = cur->right; // 向右查找
}
else {
return true; // 找到节点
}
}
return false; // 未找到
}

删除节点
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况:
- 要删除的结点无孩子结点
- 要删除的结点只有左孩子结点
- 要删除的结点只有右孩子结点
- 要删除的结点有左、右孩子结点
但是我们根据实际来看,这四种情况可以分为三种情况,简单来说
- 该节点无孩子节点:先删,然后置空
- 该节点有一个孩子节点:先连接再删
- 该节点有两个孩子节点:我们需要找一定大小的节点去替代它。
替代思路:让它的左子树最大值或者右子树最小值去替换,然后删除它(左子树max为例)
平均时间复杂度为O(logn),最坏情况为O(n)

我们这里拿3举个例子:
- 先找到目标节点3,然后找目标节点左子树的最大值left_max
- 交换目标节点cur和最大值 left_max的数据 ,标记 left_max的父节点为 parent。
找其左子树中的最大节点,即左子树中最右侧的节点,或者在其右子树中最小的节点,即右子树中最小的节点,替代节点找到后,将替代节点中的值交给待删除节点,转换成删除替代节点。
cpp
// 删除节点(非递归)
bool Erase(const T& date) {
Node* parent = nullptr;
Node* cur = root;
// 查找要删除的节点及其父节点
while (cur && cur->date != date) {
parent = cur;
if (date < cur->date) {
cur = cur->left;
}
else {
cur = cur->right;
}
}
if (cur == nullptr) return false; // 未找到要删除的节点
// 情况1:删除的节点有两个子节点
if (cur->left && cur->right) {
Node* minRight = cur->right;
Node* minRightParent = cur;
// 找到右子树中的最小节点
while (minRight->left) {
minRightParent = minRight;
minRight = minRight->left;
}
// 用最小节点的值替换当前节点值
cur->date = minRight->date;
// 转换为删除minRight(此时minRight最多有一个右子节点)
cur = minRight;
parent = minRightParent;
}
// 情况2和3:删除的节点有0或1个子节点
Node* child = cur->left ? cur->left : cur->right;
if (parent == nullptr) { // 删除的是根节点
root = child;
}
else if (parent->left == cur) {
parent->left = child;
}
else {
parent->right = child;
}
delete cur;
return true;
}

三、二叉树的应用
K模型
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
KV模型
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是就构成一种键值对。
KV代码实现
cpp
#include <iostream>
#include <string>
using namespace std;
template<class K, class V>
struct BSTNode {
BSTNode(const K& key = K(), const V& value = V())
: _pLeft(nullptr), _pRight(nullptr), _key(key), _value(value) {}
BSTNode<K, V>* _pLeft;
BSTNode<K, V>* _pRight;
K _key;
V _value;
};
template<class K, class V>
class BSTree {
typedef BSTNode<K, V> Node;
typedef Node* PNode;
public:
BSTree() : _pRoot(nullptr) {}
// 查找节点(非递归)
PNode Find(const K& key) {
PNode cur = _pRoot;
while (cur) {
if (key < cur->_key) {
cur = cur->_pLeft;
}
else if (key > cur->_key) {
cur = cur->_pRight;
}
else {
return cur;
}
}
return nullptr;
}
// 插入节点(非递归)
bool Insert(const K& key, const V& value) {
if (_pRoot == nullptr) {
_pRoot = new Node(key, value);
return true;
}
PNode parent = nullptr;
PNode cur = _pRoot;
while (cur) {
parent = cur;
if (key < cur->_key) {
cur = cur->_pLeft;
}
else if (key > cur->_key) {
cur = cur->_pRight;
}
else {
return false; // 键已存在
}
}
if (key < parent->_key) {
parent->_pLeft = new Node(key, value);
}
else {
parent->_pRight = new Node(key, value);
}
return true;
}
// 删除节点(非递归)
bool Erase(const K& key) {
PNode parent = nullptr;
PNode cur = _pRoot;
// 查找要删除的节点及其父节点
while (cur && cur->_key != key) {
parent = cur;
if (key < cur->_key) {
cur = cur->_pLeft;
}
else {
cur = cur->_pRight;
}
}
if (cur == nullptr) return false; // 未找到要删除的节点
// 情况1:删除的节点有两个子节点
if (cur->_pLeft && cur->_pRight) {
PNode minRight = cur->_pRight;
PNode minRightParent = cur;
// 找到右子树中的最小节点
while (minRight->_pLeft) {
minRightParent = minRight;
minRight = minRight->_pLeft;
}
// 用最小节点的值替换当前节点值
cur->_key = minRight->_key;
cur->_value = minRight->_value;
// 转换为删除minRight(此时minRight最多有一个右子节点)
cur = minRight;
parent = minRightParent;
}
// 情况2和3:删除的节点有0或1个子节点
PNode child = cur->_pLeft ? cur->_pLeft : cur->_pRight;
if (parent == nullptr) { // 删除的是根节点
_pRoot = child;
}
else if (parent->_pLeft == cur) {
parent->_pLeft = child;
}
else {
parent->_pRight = child;
}
delete cur;
return true;
}
// 中序遍历(递归)
void InOrder() {
_InOrder(_pRoot);
cout << endl;
}
private:
PNode _pRoot;
void _InOrder(PNode node) {
if (node) {
_InOrder(node->_pLeft);
cout << node->_key << ":" << node->_value << " ";
_InOrder(node->_pRight);
}
}
};
// 测试字典功能
void TestBSTree3() {
cout << "===== 字典测试 =====" << endl;
BSTree<string, string> dict;
dict.Insert("string", "字符串");
dict.Insert("tree", "树");
dict.Insert("left", "左边、剩余");
dict.Insert("right", "右边");
dict.Insert("sort", "排序");
cout << "请输入要查询的单词(输入q退出):" << endl;
string str;
while (cin >> str && str != "q") {
auto ret = dict.Find(str);
if (ret == nullptr) {
cout << "单词拼写错误,词库中没有这个单词: " << str << endl;
}
else {
cout << str << " 中文翻译: " << ret->_value << endl;
}
}
}
// 测试统计功能
void TestBSTree4() {
cout << "\n===== 水果统计测试 =====" << endl;
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
BSTree<string, int> countTree;
for (const auto& str : arr) {
auto ret = countTree.Find(str);
if (ret == nullptr) {
countTree.Insert(str, 1);
}
else {
ret->_value++;
}
}
cout << "水果出现次数统计:" << endl;
countTree.InOrder();
}
int main() {
TestBSTree3();
TestBSTree4();
return 0;
}

