📄前言
你是否有听说过二叉搜索树呢,如果你没有学习过二叉搜索树,你可以从本篇文章学习到二叉搜索树的知识。
二叉搜索树
概念
二叉搜索树又名二叉排序树或二叉查找树,它具有以下的特点:
- 所有节点的左节点都比父节点小。
- 所有节点的右节点都比父节点大。
- 它的左右子树都是二叉搜索树。
我们可以从上图看出,二叉搜索树的另一个重要的特性:中序遍历的二叉搜索树是升序的。
二叉搜索树的查找
二叉搜索树的查找比较的简单,因为树本身已经处于一种左小右大的情况,所以只要查找的数值比当前节点小就到左节点找,否则就是右节点。
二叉搜索树的插入
二叉搜索树的插入分为两种情况
- 空树时:直接给root节点赋值
- 非空树时:按照二叉搜索树的性质寻找适合的位置插入
二叉搜索树的删除
删除的过程就是查找的过程,如果查找成功,则删除这个节点,查找失败则返回false/NULL。
删除时分为以下三种情况:
- 删除的节点没有左右孩子
- 删除的节点拥有左右孩子的其中一个
- 删除的节点拥有左右孩子
其中第一、二种情况都很好解决。
- 没有左右孩子:只需删除当前节点即可
- 拥有左右孩子之一:把当前节点的双亲节点链入左/右孩子节点,再删除当前节点。
- 拥有左右孩子:在当前节点的左/右子树的寻找最大/小的节点,也就是中序下的第一个节点,将当前节点替换。
二叉搜索树的问题
二叉搜索树在最优情况下,也就是二叉搜索树为完全二叉树时,其平均比较次数为:
l
o
g
2
N
log_2N
log2N,但是二叉树也可能退化成为单支树,那么其平均比较次数为:
N
2
\frac{N}{2}
2N,如果要解决这种情况就必须使用AVL树,不过这就留给之后的学习吧
代码部分
结构体与类的声明
二叉树的链式结构体的定义:
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
{
public:
typedef BSTreeNode<K> Node;
bool Insert(const K& key); //插入
bool Find(const K& key); //查找
bool Erase(const K& key); //删除
void InOrder(); //中序的遍历
private:
Node* _root = nullptr; //根节点
查找
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 Insert(const K& key)
{
if (_root == nullptr) //空树的情况
{
_root = new Node(key);
return true;
}
Node* parent = nullptr; //用于记录上一个节点的位置
Node* cur = _root;
while (cur)
{ //与二叉树的搜索差异不大
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
二叉树的删除
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 (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{//右为空
if (cur == _root)
_root = cur->_left;
else
{
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{//左右都不为空
// 这里我选择了左子树的最大节点,也可以选择右节点的最小节点
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else //当subLeft没有进入while循环的情况
parent->_right = subLeft->_right;
delete subLeft;
}
return true;
}
}
return false;
}
中序遍历
void _InOrder(Node* root) //经典的中序递归遍历
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
//因为InOrder不方便遍历(没有参数),所以必须准备另一个带参数的
_InOrder(_root);
cout << endl;
}