【C++】二叉搜索树
目录
一、概念
二叉搜索树(Binary Search Tree,简称 BST)是一种特殊的二叉树,它满足以下特性:
- 若左子树不为空,其左子树中的所有值都小于该节点的值;
- 若右子树不为空,其右子树中的所有值都大于该节点的值;
- 左右子树也分别是搜索二叉树。
注:默认的搜索二叉树没有重复值。
二、功能和实现
C++中可以通过定义一个二叉树节点结构 BSTreeNode 和一个搜索二叉树类 BSTree 来实现搜索二叉树的功能。
//树节点
template <class K, class V>
struct BSTreeNode {
const K _key;
V _val;
BSTreeNode* left;
BSTreeNode* right;
BSTreeNode(K key = K(), V val = V())
: _key(key), _val(val), left(nullptr), right(nullptr)
{}
BSTreeNode(const BSTreeNode& other)
: _key(other._key), _val(other._val), left(nullptr), right(nullptr)
{}
BSTreeNode(const BSTreeNode* pother)
: _key(pother->_key), _val(pother->_val), left(nullptr), right(nullptr)
{}
};
//树
template<class K, class V>
class BSTree
{
public:
typedef BSTreeNode<K, V> Node;
typedef Node* pNode;
private:
pNode _proot;
};
常见功能包括:
- 插入值:将一个新值插入到搜索二叉树中。
- 查找值:查找搜索二叉树中是否存在指定的值,并返回相应的节点。
- 删除值:删除搜索二叉树中指定的节点。
- 中序遍历:按照节点值从小到大的顺序遍历整个搜索二叉树。
2.1 插入值
将一个新的节点插入到二叉搜索树中。
插入节点时,会根据节点的值与当前节点的值比较,并按照二叉搜索树的特性进行递归地查找插入位置。如果该值已经存在于树中,则可以选择更新节点的值或不进行任何操作。
bool Insert(K key, V val)
{
pNode pnode = new Node(key, val);
return Insert(pnode);
}
bool Insert(const Node& node)
{
return Insert(&node);
}
bool Insert(const pNode pnode)
{
return _Insert(_proot, pnode);
}
bool _Insert(pNode& cur, const pNode& pnode)
{
if (cur == nullptr)
{
cur = pnode;
return 0;
}
if (pnode->_key < cur->_key)
{
return _Insert(cur->left, pnode);
}
else if (pnode->_key > cur->_key)
{
return _Insert(cur->right, pnode);
}
else
{
return false;
}
}
2.2 查找值
在二叉搜索树中查找指定的值。
从根节点开始,根据节点的值与目标值的比较结果,决定向左子树或右子树进行递归查找,直到找到与目标值相等的节点或者遍历到叶子节点为止。如果找到了目标值,则返回相应的节点;如果未找到,则返回空。
pNode Find(const Node& node)
{
return _Find(_proot, node);
}
pNode Find(const K& key, const V& val)
{
Node node(key, val);
return _Find(_proot, node);
}
pNode _Find(pNode& pnode, const Node& node)
{
if (pnode == nullptr)
{
return nullptr;
}
if (pnode->_key == node._key)
{
return pnode;
}
else if (node._key > pnode->_key)
{
return _Find(pnode->right, node);
}
else
{
return _Find(pnode->left, node);
}
}
2.3 删除值
从二叉搜索树中删除指定的节点。
删除节点时,需要考虑三种情况:?
- 被删除节点没有子节点:直接删除该节点即可。
- 被删除节点有一个子节点:将该节点的子节点替代被删除节点的位置。
- 被删除节点有两个子节点:找到被删除节点的后继节点(即比被删除节点大的最小节点)或前置节点(即比被删除节点小的最大节点),将后继节点的值复制给被删除节点,并删除后继节点。
bool Erase(const Node& node)
{
return _Erase(_proot, node);
}
bool Erase(const K& key, const V& val)
{
Node node(key, val);
return _Erase(_proot, node);
}
bool _Erase(pNode& pnode, const Node& node)
{
if (pnode == nullptr)
{
return false;
}
if (node._key < pnode->_key)
{
return _Erase(pnode->left, node);
}
else if (node._key > pnode->_key)
{
return _Erase(pnode->right, node);
}
else
{
if (pnode->left == nullptr)
{
pnode = pnode->right;
return true;
}
else if (pnode->right == nullptr)
{
pnode = pnode->left;
return true;
}
else
{
pNode parent = nullptr;
pNode& tmp = Min_right(parent, pnode->right);
if (tmp != pnode->right)
{
tmp->left = pnode->left;
tmp->right = pnode->right;
std::swap(tmp, pnode);
parent->left = nullptr;
}
else
{
tmp->left = pnode->left;
std::swap(tmp, pnode);
}
delete tmp;
tmp = nullptr;
return true;
}
}
}
pNode& Min_right(pNode& parent, pNode& cur)
{
if (cur->left == nullptr)
{
return cur;
}
else
{
parent = cur;
return Min_right(parent, cur->left);
}
}
2.4 中序遍历
按照节点值从小到大的顺序遍历整个二叉搜索树。
中序遍历是一种基于递归的遍历方法,先遍历左子树,再访问当前节点,最后遍历右子树。通过中序遍历可以将二叉搜索树的所有节点按照从小到大的顺序输出。
void InOrder()
{
_InOrder(_proot);
}
void _InOrder(pNode cur)
{
if (cur == nullptr)
{
return;
}
_InOrder(cur->left);
cout << cur->_key << "," << cur->_val << endl;
_InOrder(cur->right);
}
2.5??二叉搜索树的实现
#include<iostream>
using namespace std;
template <class K, class V>
struct BSTreeNode {
const K _key;
V _val;
BSTreeNode* left;
BSTreeNode* right;
BSTreeNode(K key = K(), V val = V())
: _key(key), _val(val), left(nullptr), right(nullptr)
{}
BSTreeNode(const BSTreeNode& other)
: _key(other._key), _val(other._val), left(nullptr), right(nullptr)
{}
BSTreeNode(const BSTreeNode* pother)
: _key(pother->_key), _val(pother->_val), left(nullptr), right(nullptr)
{}
};
template<class K, class V>
class BSTree
{
public:
typedef BSTreeNode<K, V> Node;
typedef Node* pNode;
BSTree()
:_proot(nullptr)
{}
BSTree(const Node& node)
:_proot(new Node(node))
{}
BSTree(const pNode& pnode)
:_proot(new Node(pnode))
{}
BSTree(BSTree& other)
{
Creat(_proot,other._proot);
}
~BSTree()
{
Destory(_proot);
}
//增
bool Insert(K key, V val)
{
pNode pnode = new Node(key, val);
return Insert(pnode);
}
bool Insert(const Node& node)
{
return Insert(&node);
}
//bool Insert(const pNode pnode)
//{
// if (pnode == nullptr)
// return false;
// if (_proot == nullptr)
// {
// _proot = pnode;
// return true;
// }
// pNode parent = nullptr;
// pNode cur = _proot;
// while (1)
// {
// if (pnode->_key == cur->_key) //key相等,不存在,return false
// return false;
// else if (pnode->_key < cur->_key) //目标key 小于 当前key,向左走
// {
// parent = cur;
// if (parent->left == nullptr) //走到头,插入
// {
// parent->left = pnode;
// return true;
// }
// else
// {
// cur = parent->left;
// }
// }
// else if (pnode->_key > cur->_key) //目标key 大于 当前key,向右走
// {
// parent = cur;
// if (parent->right == nullptr) //走到头,插入
// {
// parent->right = pnode;
// return true;
// }
// else
// {
// cur = parent->right;
// }
// }
// else
// return false;
// }
// return false;
//}
bool Insert(const pNode pnode)
{
return _Insert(_proot, pnode);
}
//查
pNode Find(const Node& node)
{
return _Find(_proot, node);
}
pNode Find(const K& key, const V& val)
{
Node node(key, val);
return _Find(_proot, node);
}
//删
bool Erase(const Node& node)
{
return _Erase(_proot, node);
}
bool Erase(const K& key, const V& val)
{
Node node(key, val);
return _Erase(_proot, node);
}
void Print()
{
_Print(_proot);
}
private:
void Creat(pNode& proot1, const pNode& proot2)
{
if (proot2 == nullptr)
{
proot1 = nullptr;
return;
}
proot1 = new Node(proot2);
Creat(proot1->left, proot2->left);
Creat(proot1->right, proot2->right);
}
void Destory(pNode& proot)
{
if (proot == nullptr)
{
return;
}
if (proot->left != nullptr)
{
Destory(proot->left);
proot->left = nullptr;
}
if (proot->right != nullptr)
{
Destory(proot->right);
proot->right = nullptr;
}
delete(proot);
proot = nullptr;
}
bool _Insert(pNode& cur, const pNode& pnode)
{
if (cur == nullptr)
{
cur = pnode;
return 0;
}
if (pnode->_key < cur->_key)
{
return _Insert(cur->left, pnode);
}
else if(pnode->_key > cur->_key)
{
return _Insert(cur->right, pnode);
}
else
{
return false;
}
}
pNode _Find(pNode& pnode, const Node& node)
{
if (pnode == nullptr)
{
return nullptr;
}
if (pnode->_key == node._key)
{
return pnode;
}
else if(node._key > pnode->_key)
{
return _Find(pnode->right, node);
}
else
{
return _Find(pnode->left, node);
}
}
bool _Erase(pNode& pnode, const Node& node)
{
if (pnode == nullptr)
{
return false;
}
if (node._key < pnode->_key)
{
return _Erase(pnode->left, node);
}
else if (node._key > pnode->_key)
{
return _Erase(pnode->right, node);
}
else
{
if (pnode->left == nullptr)
{
pnode = pnode->right;
return true;
}
else if (pnode->right == nullptr)
{
pnode = pnode->left;
return true;
}
else
{
pNode parent = nullptr;
pNode& tmp = Min_right(parent, pnode->right);
if (tmp != pnode->right)
{
tmp->left = pnode->left;
tmp->right = pnode->right;
std::swap(tmp, pnode);
parent->left = nullptr;
}
else
{
tmp->left = pnode->left;
std::swap(tmp, pnode);
}
delete tmp;
tmp = nullptr;
return true;
}
}
}
pNode& Min_right(pNode& parent, pNode& cur)
{
if(cur->left == nullptr)
{
return cur;
}
else
{
parent = cur;
return Min_right(parent, cur->left);
}
}
void _Print(pNode cur)
{
if (cur == nullptr)
{
return;
}
_Print(cur->left);
cout << cur->_key << "," << cur->_val << endl;
_Print(cur->right);
}
private:
pNode _proot;
};
三、二叉搜索树的应用
- K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。 - KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
该种方式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对。
本篇实现的二叉搜索树就是KV模型。
四、二叉搜索树的性能分析
二叉搜索树的性能分析涉及到其在插入、查找和删除等操作上的平均和最坏情况下的时间复杂度,并且还需要考虑树的平衡性。
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
查找操作:
平均情况下,对于具有随机插入顺序的数据,二叉搜索树的查找操作时间复杂度为 O(log n),因为在平衡情况下,树的高度通常保持在 O(log n)。
最坏情况下,如果插入的数据是有序的,树会变得极不平衡,查找操作的时间复杂度可能达到 O(n),退化成链表。
?
平衡性:
二叉搜索树的性能与其平衡性密切相关。在最坏情况下,二叉搜索树可能会退化成链表,导致插入、查找和删除操作的时间复杂度变得非常高。为了保持良好的性能,可以使用平衡二叉搜索树(如AVL树、红黑树)来确保树的高度保持在较低的水平(后面会学习到),从而使得插入、查找和删除等操作的时间复杂度保持在较低的范围内。
总体而言,二叉搜索树在平均情况下具有较高的性能,但在最坏情况下可能性能下降,需要额外的操作来保持平衡性。因此,在实际应用中,可以考虑使用平衡二叉搜索树或者其他更适合具体应用场景的数据结构来提高性能。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!