二叉搜索树的概念
二叉搜索树又称二叉排序树,它或者是一个空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
二叉搜索树操作
1、二叉搜索树的查找
2、二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接插入
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
3、二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4种情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
- 情况b:删除该结点且使被删除结点的双亲结点指向被删除结点的左孩子结点
- 情况c:删除该结点且使被删除结点的双亲结点指向被删除结点的右孩子结点
- 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除结点中,再来处理该结点的删除问题
二叉搜索树的实现
#include <iostream>
using namespace std;
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
{
typedef BSTNode<T> Node;
typedef Node* PNode;
public:
BSTree()
:_pRoot(nullptr)
{}
~BSTree() //不能直接递归销毁
{
_Destroy(_pRoot);
}
//检查元素在不在树内
PNode Find(const T& data)
{
PNode pCur = _pRoot;
while (pCur)
{
if (data == pCur->_data)
return pCur;
else if (data < pCur->_data)
return pCur->_pLeft;
else
return pCur->_pRight;
}
return nullptr;
}
//插入
bool Insert(const T& data)
{
//如果树为空,直接插入
if (nullptr == _pRoot)
{
_pRoot = new Node(data);
return true;
}
//按照二叉搜索树的性质查找data在树中的插入位置
PNode pCur = _pRoot;
//记录pCur的双亲,因为新元素最终插入在pCur双亲左右孩子的位置
PNode pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (data < pCur->_data)
pCur = pCur->_pLeft;
else if (data > pCur->_pdata)
pCur = pCur->_pRight; //元素已经在树中存在
else
return false;
}
//插入元素
pCur = new Node(data);
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
return true;
}
//删除元素
bool Erase(const T& data)
{
//如果树为空,删除失败
if (nullptr == _pRoot)
return false;
//查找data在树中的位置
PNode pCur = _pRoot;
PNode pParent = nullptr;
while (pCur)
{
if (data == pCur->_data)
break;
else if (data < pCur->_data)
{
pParent = pCur;
pCur = pCur->_pLeft;
}
else
{
pParent = pCur;
pCur = pCur->_pRight;
}
}
//data不在二叉搜索树中,无法删除
if (nullptr == pCur)
return false;
//分以下情况进行删除
Node* del = cur;
if (nullptr == pCur->_pRight)
{
//当前节点只有左孩子或者左孩子为空 ----- 可直接删除
if (pParent == nullptr)
_pRoot = _pRoot->_pLeft; //要删节点是根节点
if (pParent->_pLeft == pCur)
pParent->_pLeft = pCur->_pLeft;
else
pParent->_pRight = pCur->_pLeft;
}
else if (nullptr == pCur->_pLeft)
{
//当前节点只有右孩子 ----- 可直接删除
if (pParent == nullptr)
_pRoot = _pRoot->_pRight; //要删节点是根节点
if (pParent->_pLeft == pCur)
pParent->_pLeft = pCur->_pRight;
else
pParent->_pRight = pCur->_pRight;
}
else
{
//当前节点左右孩子都存在,直接删除不好删除,可以在其子树中找一个替代结点,
//比如:找其左子树中的最大结点,即左子树中最右侧的结点,或者在其右子树中最小的结点,即右子树中最小的结点
//替代结点找到后,将替代结点中的值交给待删除结点,转换成删除替代结点
Node* replace = pCur->_pRight; //用右树最左进行替代
Node* pre = pCur; //替代节点的双亲节点,关键!!
while (replace->_pLeft) //找到右树最左结点
{
pre = replace;
replace = replace->_pLeft;
}
pCur->_data = replace->_data;
if (pre->_pLeft == replace) //替代节点的双亲节点与它的下个结点连接
pre->_pLeft = replace->_pRight;
else pre->_pRight = replace->_pRight;
del = replace;
}
delete del;
return true;
}
void InOrder()
{
_InOrder(_pRoot);
}
private:
void _InOrder(PNode pRoot)
{
if (pRoot)
{
_InOrder(pRoot->_pLeft);
cout << pRoot->_data << " ";
_InOrder(pRoot->_pRight);
}
}
void _Destroy(PNode& pRoot)
{
if (pRoot)
{
_Destroy(pRoot->_pLeft);
_Destroy(pRoot->_pRight);
pRoot = nullptr;
}
}
private:
PNode _pRoot;
};
二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2