文章目录
前言
路漫漫其求远兮,吾将上下而求索;
一、什么是二叉搜索树
二叉搜索树(Binary Search Tree,BST),又称二叉排序树,是一种特殊的二叉树结构,它满足以下性质之一:
-
是一棵空树;
-
或满足以下所有条件:
-
若左子树非空,则左子树上所有结点的值均小于或等于根结点的值;
-
若右子树非空,则右子树上所有结点的值均大于或等于根结点的值;
-
左子树和右子树也分别是二叉搜索树。
-
需要注意的是,根据实现需求,二叉搜索树可分为 不允许重复值(无冗余) 和 允许重复值(有冗余) 两种设计;
这种结构支持高效的查找、插入和删除操作,平均时间复杂度为 O(log n),但在最坏情况下(如退化为链表)可能降至 O(n)。所以综合而言,二叉搜索树的查找、插入、删除的效率为O(N),如果想要维持其O(logN)的效率,需要对其平衡进行控制,也就是AVL树、红黑树;

二、结构
定义搜索二叉树的节点结构:
//定义节点
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
{
typedef BSTNode<K> Node;
public:
BSTree():_root(nullptr){}
//拷贝构造,需要遍历+构造节点
BSTree(const Node& t){}
//赋值运算符重载 --》 利用拷贝构造的现代写法
Node& operator=(Node t){}
//析构函数 --》后序遍历释放节点
~BSTree(){}
//插入
bool Insert(const K& key){}
//中序遍历
void Inorder(){}
//查找
bool Find(const K& key){}
//删除
bool Erase(const K& key){}
private:
Node* _root;
};
三、实现代码
拷贝构造函数
需要递归地遍历该二叉搜索树当中的节点,创建节点并处理连接关系;
参考代码如下:
//拷贝构造,需要遍历+构造节点
BSTree(const Node& t)
{
_root = copy(t._root);
}
Node* copy(Node* root)
{
//前序遍历构造一棵树
if(root==nullptr) return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->left = copy(root->_left);
newRoot->right = copy(root->_right);
return newRoot;
}
赋值运算符重载
此处实现巧妙利用拷贝构造函数(传值传参会进行拷贝,那么我们在赋值运算符重载中利用swap 交换this 与 传值传参的局部对象即可实现)
参考代码如下:
//赋值运算符重载 --》 利用拷贝构造的现代写法
Node& operator=(Node t)
{
std::swap(_root , t._root);
return *this;
}
析构函数
析构的实现需要后序遍历这颗二叉搜索树,释放节点
参考代码如下:
//析构函数 --》后序遍历释放节点
~BSTree()
{
Destory(_root);
_root = nullptr;
}
void Destory(Node* root)
{
if(root==nullptr) return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
插入
过程:
- 如果根节点为空,直接创建一个节点然后插入
- 如果根节点不为空,需要找到插入值key 合适的位置进行插入:当key 小于当前节点cur中存储的值,那么cur = cur->_left , 如果key 大于当前节点cur 当中存储的值,那么cur = cur->_right;至于key等于当前节点cur 中存储值的情况,需要结合实际情况(是否支持冗余)来决定,此处实现为不冗余;
参考代码如下:
//插入数据,不允许插入冗余的数据
bool Insert(const K& key)
{
//空树的话,直接创建节点
if(_root==nullptr)
{
_root = new Node(key);
return true;
}
//找到合适的位置,利用前后指针
Node* cur = _root, *prev = nullptr;
while(cur)
{
if(key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else if(key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else{
//数据已经存在
return false;
}
}
//找到了合适的位置,需要创建节点并处理链接关系
cur = new Node(key);
//判断这个节点需要放在prev 的左边还是右边
if(key < prev->_key)
{
prev->_left = cur;
}
else{
prev->_right = cur;
}
return true;
}
中序遍历
二叉搜索树中序遍历的结果是有序的,我们实现的Inorder 需要传参,而在外部由于访问限定符的限制不易于外部调用传参,所以我们可以考虑在内部封装一层,即: void _Inorder(Node* root)
参考代码如下:
//中序遍历
void Inorder()
{
_Inorder(_root);
std::cout << std::endl;
}
void _Inorder(Node* root)
{
if(root == nullptr) return;
_Inorder(root->_left);
std::cout << root->_key << " ";
_Inorder(root->_right);
}
查找
查找的逻辑与插入数据的逻辑较为相似,比对当前节点cur 当中存储的值与 key 的大小关系,然后决定当前节点 cur 应该往左还是往右走;
参考代码如下:
//查找数据
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;
}
删除
删除=查找存储值为key 的节点+处理删除之后的链接关系
需要分情况讨论:
- 所删除的节点没有孩子节点 --》直接删除、处理其父节点的指针指向
- 所删除的节点只有左孩子,需要其父节点”接管“该所删除节点的左孩子;
- 所删除的节点只有右孩子,需要其父节点”接管“所删除节点的右孩子;
- 所删除的节点既有左孩子又有右孩子,此时需要使用交换删除法,即在其左子树中找到最大的节点(或者是右子树中最小的节点)与所删除的节点进行交换,然后再删除;
需要注意的是,在实现删除逻辑的时候,需要判断所删除节点是否为根节点(如果为根节点,需要处理成员变量 _root)
参考代码如下:
//删除一个节点 --》 需要判断当前这个节点是否存在孩子节点,需要对孩子节点的个数情况进行判断
bool Erase(const K& key)
{
//查找 + 判断子节点的情况
Node* cur = _root, * prev = nullptr;
while(cur)
{
if(key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else if(key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else{
//找到了节点,判断这个节点是否存在子节点;当然,找到的节点也有可能为根节点,那么prev 为 nullptr
if(cur->_left == nullptr)
{
if(prev == nullptr)
{
_root = cur->_right;
}
else
{
//不存在左孩子,可能存在右孩子,判断cur 当前在 prev 的左边还是右边,然后将cur 的右孩子给给prev
if(cur == prev->_left)
{
prev->_left = cur->_right;
}else{
prev->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right == nullptr)
{
if(prev==nullptr)
{
_root = cur->_left;
}
else
{
if(cur == prev->_left)
{
prev->_left = cur->_left;
}else{
prev->_right = cur->_left;
}
}
delete cur;
}
else
{
//存在两个孩子,交换+删除的思想,需要找到cur 左子树当中的最大节点或者右子树当中的最小节点
Node* replaceParent = cur, *replace = cur;
while(replace->_left)//此处实现为找到右子树当中的最小节点,即最左节点
{
replaceParent = replace;
replace = replace->_left;
}
std::swap(cur->_key , replace->_key);
//处理连接关系,当前的replace 可能存在右孩子
if(replace == replaceParent->_left)
{
replaceParent->_left = replace->_right;
}
else
{
replaceParent->_right = replace ->_left;
}
delete replace;
}
return true;
}
}
return false;
}
总结
动手写一写吧~
880

被折叠的 条评论
为什么被折叠?



