目录
一、二叉搜索树概念
当然可以。二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树数据结构,它在计算机科学中有广泛的应用,尤其是在需要快速查找、插入和删除操作的场景中。
1、二叉搜索树的定义
二叉搜索树是一种具有以下性质的二叉树:
- 左子树的性质:如果一个节点有左子树,则左子树上所有节点的值均小于它的根节点的值。
- 右子树的性质:如果一个节点有右子树,则右子树上所有节点的值均大于它的根节点的值。
- 递归性质:它的左、右子树也分别为二叉搜索树。
这些性质使得二叉搜索树非常适合用于有序集合的存储,并且使得查找、插入和删除等操作的时间复杂度在平均情况下为 O(),其中 nn 是树中节点的数量。
2、二叉搜索树的性质
- 中序遍历有序:二叉搜索树的中序遍历(左-根-右)会产生一个升序排列的节点值序列。
- 查找最小/最大元素:在二叉搜索树中查找最小元素,只需一直沿着左子树走到底;查找最大元素,只需一直沿着右子树走到底。
中序遍历:1 3 4 6 7 8 10 13 14
3、二叉搜索树的局限性
虽然二叉搜索树提供了高效的查找、插入和删除操作,但它也有一些局限性:
- 不平衡问题:在最坏的情况下,如果连续插入的键值都是递增或递减的,二叉搜索树可能会退化成一个链表,导致时间复杂度退化为 O(n)O(n)。
- 自平衡二叉搜索树:为了避免不平衡问题,人们发明了一些自平衡的二叉搜索树,如AVL树和红黑树等,它们通过在插入和删除操作期间自动调整树的结构来保持树的高度尽可能平衡,从而保证操作的平均时间复杂度为 O(logn)O(logn)。
4、应用场景
二叉搜索树因其高效的查找、插入和删除性能,在多种场合中得到了广泛应用,例如:
- 文件系统:用于索引文件和目录。
- 数据库管理系统:用于索引表中的记录。
- 符号表:用于存储键值对,例如编译器中的符号表。
- 优先队列:通过适当的键值映射,可以构建优先队列。
二、二叉搜索树的实现
namespace hbr
{
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:
bool Insert(const K& key){}
bool Find(const K& key){}
bool Erase(const K& key){}
void _InOrder(Node* root){}
private:
Node* _root = nullptr;
};
}
首先,定义了一个 BSTreeNode
结构体,表示二叉搜索树的节点。每个节点包含一个键值 _key
,以及指向左子节点 _left
和右子节点 _right
的指针。
接下来,定义了一个 BSTree
类,表示二叉搜索树。该类包含一个私有成员 _root
,指向树的根节点。
BSTree
类提供以下公有成员函数:
-
Insert
函数用于向二叉搜索树中插入一个节点。它接受一个键值key
作为参数,并根据二叉搜索树的性质找到合适的位置插入节点。 -
Find
函数用于在二叉搜索树中查找一个节点。它接受一个键值key
作为参数,并根据二叉搜索树的性质进行查找,返回是否找到该节点。 -
Erase
函数用于从二叉搜索树中删除一个节点。它接受一个键值key
作为参数,并根据二叉搜索树的性质找到要删除的节点,并进行删除操作。 -
_InOrder 函数用于中序遍历,由于二叉搜索树的性质使用中序遍历可以按照升序输出每个节点。
namespace test
{
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 默认构造函数
BSTree() = default;
// 复制构造函数
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root); // 深拷贝整个树
}
// 赋值运算符重载
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root); // 使用交换技巧完成赋值操作
return *this;
}
// 析构函数
~BSTree()
{
Destroy(_root); // 销毁整个树
}
// 插入一个新节点
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key); // 如果树为空,则创建根节点
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right; // 向右子树移动
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left; // 向左子树移动
}
else
{
return false; // 如果键已存在,则不插入
}
}
cur = new Node(key); // 创建新节点
if (parent->_key < key)
{
parent->_right = cur; // 插入到父节点的右子树
}
else
{
parent->_left = cur; // 插入到父节点的左子树
}
return true;
}
// 查找一个节点
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right; // 向右子树移动
}
else if (cur->_key > key)
{
cur = cur->_left; // 向左子树移动
}
else
{
return true; // 找到了键值
}
}
return false; // 没有找到键值
}
// 删除一个节点
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right; // 向右子树移动
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left; // 向左子树移动
}
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; // 替换父节点的右子树
}
}
delete cur; // 删除旧节点
}
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; // 替换父节点的右子树
}
}
delete cur; // 删除旧节点
}
else // 既有左子树又有右子树的情况
{
Node* pminRight = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left; // 找到右子树中的最小节点
}
cur->_key = minRight->_key; // 使用右子树的最小值替换当前节点的键值
if (pminRight->_left == minRight)
{
pminRight->_left = minRight->_right; // 替换右子树中最小节点的位置
}
else
{
pminRight->_right = minRight->_right; // 替换右子树中最小节点的位置
}
delete minRight; // 删除旧的最小节点
}
return true; // 成功删除
}
}
return false; // 没有找到要删除的键值
}
// 查找一个节点
bool Find(const K& key)
{
return _FindR(_root, key); // 使用递归函数查找
}
// 插入一个新节点(递归版本)
bool InsertR(const K& key)
{
return _InsertR(_root, key); // 使用递归函数插入