二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树::
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
定义二叉搜索树的结点:
template <class T>
struct BSTreeNode {
T _data;
BSTreeNode<T>* _pLeft;
BSTreeNode<T>* _pRight;
BSTreeNode(const T& data = T()) : _pLeft(nullptr),
_pRight(nullptr),
_data(data) {}
};
二叉搜索树定义:
template <class T>
class BSTree {
typedef BSTreeNode<T> Node;
public:
对外接口
private:
Node* _pRoot;
};
二叉搜索树的插入
a. 树为空,则直接插入
if (nullptr == _pRoot ) {
_pRoot = new Node(data);
return true;
}
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
//找当前结点在二叉搜索树中的位置
Node* pCur = _pRoot;
Node* pParent = nullptr;
while (pCur) {
//当pCur为空,则记录pCur的父节点
pParent = pCur; //保护父节点
if (data < pCur->_data)
pCur = pCur->_pLeft;
else if (data > pCur->_data)
pCur = pCur->_pRight;
else
return false;
}
//插入结点
pCur = new Node(data);
if (data > pParent->_data)
pParent->_pRight = pCur;
else
pParent->_pLeft = pCur;
return true;
二叉搜索树的查找
二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回。
//找到要删除结点,及记录要删除的父亲结点
Node* pCur = _pRoot;
Node* pParent = nullptr;
while (pCur) {
if (data < pCur->_data) {
pParent = pCur;
pCur = pCur->_pLeft;
} else if (data > pCur->_data) {
pParent = pCur;
pCur = pCur->_pRight;
} else {
break;
}
}
//没找到则返回
if (nullptr == pCur) {
return false;
}
然后要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点(注:要判断要删除结点是否为根结点,还要判断删除结点是父亲结点的左孩子还是右孩子)
//只有左孩子
if (nullptr == pCur->_pRight) {
if (nullptr == pParent) { //为根节点
_pRoot = pCur->_pLeft;
} else {
//为父节点的左孩子
if (pCur == pParent->_pLeft)
pParent->_pLeft = pCur->_pLeft;
//为父节点的右孩子
else
pParent->_pRight = pCur->_pLeft;
}
delete pCur;
}
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点(注:要判断要删除结点是否为根结点,还要判断删除结点是父亲结点的左孩子还是右孩子)
//只有右孩子
else if (nullptr == pCur->_pLeft) {
if (nullptr == pParent) { //为根节点
_pRoot = pCur->_pRight;
} else {
//是父节点的左孩子
if (pCur == pParent->_pLeft)
pParent->_pLeft = pCur->_pRight;
//是父节点的右孩子
else
pParent->_pRight = pCur->_pRight;
}
delete pCur;
}
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
- 找到替删结点(真正要删除的结点),可以删除左子树最右(大)结点,也可以删除右子树最左(小)结点,以删除右子树最左结点为例
//在右子树找替代将删除的替代结点
Node* pDel = pCur->_pRight;
pParent = pCur;
while (pDel->_pLeft) {
pParent = pDel;
pDel = pDel->_pLeft;
}
- 把替删结点的数据赋给目标删除结点
//将替代结点的值--->待删除的结点
pCur->_data = pDel->_data;
- 删除替删结点
①替删结点是父亲结点的左孩子(因为替删结点已经最左,只要把替删结点的右孩子接到父亲结点即可)
②替删结点是父亲结点的右孩子(可能要删除的右子树没有最左,替删结点是目标结点的右孩子)
//如果替代结点是父节点的左孩子,因为替代结点已经最左不会有左孩子,直接把右孩子接到父节点即可
if (pDel == pParent->_pLeft)
pParent->_pLeft = pDel->_pRight;
else //替代结点是父节点的右孩子(替代结点就是要删除结点的右孩子)
pParent->_pRight = pDel->_pRight;
delete pDel;