概念:
二叉搜索树(Binary Search Tree),(又称:二叉查找树,二叉排序树):它或者是一颗空树,或者是一颗具有以下性质的二叉树:
- 若它的左子树不为空, 则左子树上所有节点的值均小于根节点的值;
- 若它的右子树不为空, 则右子树上所有节点的值均大于根节点的值;
- 它的左右子树也为二叉搜索树;
算法实现:
二叉搜索树的操作主要有:
1. 查找: 递归查找是否存在data.
2. 插入:
- 树为空, 直接插入, 返回true
- 树不为空, 按照二叉搜索树的性质查找插入位置, 插入新节点
3. 删除:
- 要删除的结点为叶子结点: 直接删除.
- 要删除的结点只有左子树结点,: 删除该结点后, 将它的左子树整体移动到删除结点的位置.
- 要删除的结点只有右子树结点: 删除该结点后, 将它的右子树整体移动到删除结点的位置.
- 要删除的结点左,右子树结点都存在: 直接删除的操作比较复杂, 可以在其子树中找到一个代替节点,用代替节点的值直接覆盖要删除的结点,从而转换成删除代替节点.-----代替结点的选择:可以选其左子树中的最大节点,即左子树中最右侧的结点, 或者其右子树中的最小结点, 即右子树中最左侧的结点.
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: _pleft(nullptr), _pright(nullptr), _data(data)
{
}
BSTNode<T>* _pleft;
BSTNode<T>* _pright;
T _data;
};
template<class T>
class BSTree
{
private:
typedef BSTNode<T> Node;
typedef Node* PNode;
public:
PNode _proot;
public:
// 构造
BSTree()
: _proot(nullptr)
{}
// 析构
~BSTree()
{
Destory(_proot);
};
// 二叉搜索树的查找
PNode Find(const T& data)
{
// 思路: 首先结点不应为空
// 1. 如果节点的值 data = 查找的值data, 返回该节点
// 2. 如果节点的值 data < 查找的值data, 在该结点的右子树中查找
// 3. 如果节点的值 data > 查找的值data, 在该结点的左子树中查找
// 4. 遍历完成未找到则返回 nullptr;
PNode cur = _proot;
while (cur)
{
if (cur->_data == data)
return cur;
else if (cur->_data < data)
{
cur = cur->_pright;
} else
cur = cur->_pleft;
}
return nullptr;
}
bool Insert(const T& data)
{
// 思路:
// 1. 如果根节点为空,则直接插入(根结点直接赋值) ---返回true
// 2. 如果不为空, 则按照二叉搜索树性质 查找应该插入的位置,然后插入
if (_proot == nullptr)
{
_proot = new Node(data);
return true;
}
PNode cur = _proot;
PNode parent = nullptr;
while (cur)
{
parent = cur;
if (data < cur->_data)
cur = cur->_pleft;
else if (data > cur->_data)
cur = cur->_pright;
else
return false; // 如果插入的结点已经存在, 则插入失败
}
cur = new Node(data);
// 将新插入的结点连接到树上
if (data > parent->_data)
parent->_pright = cur;
else
parent->_pleft = cur;
return true;
}
bool Erase(const T& data)
{
// 如果树为空, 则直接返回false,删除失败
if (_proot == nullptr)
return false;
// 遍历查找要删除结点, 并记录该节点的父节点
PNode parent = nullptr;
PNode cur = _proot;
while (cur)
{
if (data == cur->_data)
break;
else if (data > cur->_data)
{
parent = cur;
cur = cur->_pright;
}
else
{
parent = cur;
cur = cur->_pleft;
}
}
// 如果要删除结点不存在, 则删除失败
if (cur == nullptr)
return false;
// 找到要删除结点后, 共有四种情况
// 1.该结点无孩子结点
// 2.该结点只有左孩子结点
// 3.该结点只有右孩子结点
// 4.该结点左右孩子结点都存在
// 第一种情况可以与 2或3情况一并处理
// 情况1,2
if (cur->_pright == nullptr)
{
// 如果要删除的结点为根节点,且其只有左孩子结点
if (cur == _proot)
{
_proot = cur->_pleft;
//delete Node(cur);
}
else
{
if (cur == parent->_pleft)
parent->_pleft = cur->_pleft;
else
parent->_pright = cur->_pleft;
}
}
// 情况3
else if (cur->_pleft == nullptr)
{
if (cur == _proot)
_proot = cur->_pright;
else
{
if (cur == parent->_pleft)
parent->_pleft = cur->_pright;
else
parent->_pright = cur->_pright;
}
}
// 情况4
// 该结点左右子树都存在的情况,不能直接删除
// 方法1: 在该结点的左子树中找到最大值(即最右侧的结点)将该结点的值赋给要删除的结点,然后在删除代替节点
// 方法2: 在该结点的右子树中找到最小值---处理方式与方法1类似
else
{
if (cur->_pleft != nullptr && cur->_pright != nullptr)
{
// 找左子树最大节点替换要删除的结点
PNode replace = cur->_pleft;
PNode parent = cur;
while (replace->_pright)
{
parent = replace;
replace = replace->_pright;
}
cur->_data = replace->_data;
if (replace == parent->_pleft)
parent->_pleft = replace->_pleft;
else
parent->_pright = replace->_pleft;
delete replace;
replace = nullptr;
}
return true;
}
return false;
}
private:
void Destory(PNode _proot)
{
if (_proot)
{
Destory(_proot->_pleft);
Destory(_proot->_pright);
}
_proot = nullptr;
}
};
性能分析:
二叉搜索树的基本操作都是基于查找的, 查找的效率代表了二叉搜索树中其它操作的性能.
若一个二叉搜索树具有N个结点, 若每个元素查找效率相同, 则二叉搜索树的平均查找长度与结点在树中的深度成正比.
也就是说,不论哪一种操作, 所花费的时间都和树的深度成正比
- 最优情况下, 二叉搜索树为 完全二叉树, 其平均比较次数为: log N
- 最差情况下, 二叉搜索树为 单支树, 其平均比较次数为: N/2
二叉搜索树虽然可以缩短查找的效率, 但是如果数据有序或者接近有序,二叉搜索树将会退化为单支树, 查找元素相当于在顺序表中搜索元素,效率低下. 由此引入了 AVL树.
AVL树的实现及其性能分析