目录
引言
如何在大量数据中进行高效的搜索、插入和删除呢?就比如:
想象一下,你正在图书馆的书架上寻找一本特定编号的书籍。如果书籍杂乱无章,你可能需要逐本翻找;但如果它们按编号从小到大排列,你可以快速定位目标。二叉搜索树(Binary Search Tree, BST) 正是实现这种“高效查找”的核心数据结构之一。本文将深入解析一下其特点、工作原理及操作实现。
一、二叉搜索树的定义与性质
1.1、什么是二叉搜索树?
二叉搜索树(BST)是一颗二叉树,是重要的树形结构之一。其性质,简单来说就是:
- 对任意节点,其左子树的所有节点值均小于该节点值,右子树的所有节点值均大于该节点值。
- 左子树和右子树本身也是二叉搜索树。
结构如下:
1.2、优势
-
高效:时间复杂度跟搜索树的高度有关,跟节点个数无关,插入、删除、查找操作的平均时间复杂度为 O(log n)。
-
有序遍历:中序遍历(左-根-右)可以得到一个升序序列。
二、二叉搜索树的核心操作
2.1、整体结构的定义
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:
// ...其他操作
private:
Node* _root; //指向二叉搜索树的根节点
};
2.2、查找(Find)
逻辑:从根节点开始,逐层比较目标值与当前节点:
-
若目标值等于当前节点值,返回节点。
-
若目标值小于当前节点值,递归查找左子树。
-
若目标值大于当前节点值,递归查找右子树。
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;
}
2.3、插入(Insert)
逻辑:跟查找差不多,查找到合适位置就插入新节点。如果树里面有该值就不插入了。
步骤:
- 如果树为空,创建新节点作为树节点。
- 不为空就找位置,插入值比当前节点值小就去左树找,否则就去右树找。
bool Insert(const K& key) // 插入一个键值为key的节点
{
if (_root == nullptr) // 如果树为空
{
_root = new Node(key); // 创建一个新的根节点
return true; // 插入成功
}
Node* parent = nullptr; // 用于记录当前节点的父节点
Node* cur = _root; // 从根节点开始查找插入位置
while (cur) // 遍历树,直到找到合适的插入位置
{
if (key < cur->_key) // 如果待插入的键值小于当前节点的键值
{
parent = cur; // 更新父节点为当前节点
cur = cur->_left; // 向左子树移动
}
else if (key > cur->_key) // 如果待插入的键值大于当前节点的键值
{
parent = cur; // 更新父节点为当前节点
cur = cur->_right; // 向右子树移动
}
else // 如果待插入的键值等于当前节点的键值
{
return false; // 表示树中已存在该键值,插入失败
}
}
cur = new Node(key); // 创建一个新的节点
if (key < parent->_key) // 根据键值大小关系,将新节点插入到父节点的左子树
{
parent->_left = cur;
}
else // 或者插入到父节点的右子树
{
parent->_right = cur;
}
return true; // 插入成功
}
2.4、删除(erase)
逻辑:查找到要删除的节点时,删除操作分三种情况处理,确保删除后仍是一颗搜索二叉树。
情况一:当删除的是叶子节点。
直接删除即可
情况二:删除仅有一个子节点的节点。
用子节点替代被删除节点。
情况三:删除有两个子节点的节点。
找替代节点。找左子树的最大节点(或右子树的最小节点)替代被删除的节点,然后删除替代节点即可。
代码示例:
bool erase(const K& key) // 删除键值为key的节点
{
if (_root == nullptr)
{
return false;
}
Node* parent = nullptr;
Node* cur = _root;
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)
{
// 如果待删除节点是根节点
if (cur == _root)
{
_root = cur->_right; // 将根节点更新为右子节点
}
else // 如果待删除节点不是根节点
{
// 根据待删除节点是父节点的左子节点还是右子节点,更新父节点的对应指针
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
// 处理待删除节点右子树为空的情况
else if (cur->_right == nullptr)
{
// 如果待删除节点是根节点
if (cur == _root)
{
_root = cur->_left; // 将根节点更新为左子节点
}
else // 如果待删除节点不是根节点
{
// 根据待删除节点是父节点的左子节点还是右子节点,更新父节点的对应指针
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
// 处理待删除节点左右子树都不为空的情况
else
{
// 找替代节点,这里选择左子树中最大的节点作为替代节点
Node* parent = cur; // 重新定义一个变量parent,用于记录替代节点的父节点
Node* leftMax = cur->_left; // 从待删除节点的左子节点开始查找左子树中最大的节点
while (leftMax->_right) // 找到左子树中最大的节点
{
parent = leftMax;
leftMax = leftMax->_right;
}
// 将替代节点的键值与待删除节点的键值交换
swap(leftMax->_key, cur->_key);
// 删除替代节点
if (parent->_left == leftMax)
{
parent->_left = leftMax->_left;
}
else
{
parent->_right = leftMax->_left;
}
cur = leftMax; // 将cur指向替代节点
}
delete cur; // 删除待删除节点或替代节点
return true; // 删除成功
}
}
return false; // 如果遍历完树都没有找到待删除节点,返回false
}
三、BST局限性:平衡问题
3.1、时间退化问题
当数据按顺序插入如(5,4,3,2,1),BST会退化成链表,时间复杂度会退化成O(n).
3.2、解决方案:平衡二叉搜索树
因为访问二叉搜索树的效率跟树的高度有关,所以在不破坏搜索树结构的同时,通过自动调整树结构保持低高度:
-
AVL树:通过旋转保持左右子树高度差≤1。详见:二叉平衡搜索树:AVL树-CSDN博客
-
红黑树:通过颜色标记和旋转实现近似平衡,C++的std::map
四、完整代码实现(C++)
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:
BSTree()
:_root(nullptr)
{}
BSTree(BSTree<K>& t)
{
_root = Copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
}
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 Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(const Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
bool erase(const K& key)
{
if (_root == nullptr)
{
return false;
}
Node* parent = nullptr;
Node* cur = _root;
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)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
else//左右都不为空时
{
//找替代节点
//找左子树最大的节点或找右子树最小的节点
Node* parent = cur;
Node* leftMax = cur->_left;
while (leftMax->_right)
{
parent = leftMax;
leftMax = leftMax->_right;
}
swap(leftMax->_key, cur->_key);
if (parent->_left == leftMax)
{
parent->_left = leftMax->_left;
}
else
{
parent->_right = leftMax->_left;
}
cur = leftMax;
}
delete cur;
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* 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;
}
void Destroy(Node*& root)
{
if (root == nullptr)
{
return;
}
Destroy(root->_left);
Destroy(root->_right);
delete root;
root = nullptr;
}
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;
//1、左为空
//2、右为空
//3、左右都不为空
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;
}
swap(leftMax->_key, root->_key);
return _eraseR(root->_left, key);
}
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
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 _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (key < root->_key)
{
return _FindR(root->_left, key);
}
else if (key > root->_key)
{
return _FindR(root->_right, key);
}
else
{
return true;
}
}
private:
Node* _root;
};