一、二叉排序树的概念
二叉排序树(也称二叉搜索树或二叉查找树),它可能是一棵空树,或者是具有以下特性的二叉树:
- 若左子树非空,则左子树上所有结点的值均小于根结点的值。
- 若右子树非空,则右子树上所有结点的值均大于根结点的值。
- 左、右子树也分别是一棵二叉排序树
根据二叉排序树的定义,左子树结点值< 根结点值<右子树结点值,因此对二叉排序树进行中序遍历,可以得到一个递增的有序序列。例如,上图所示二叉排序树的中序遍历序列为1,3,4,6,7,8,10,13,14。
二、二叉排序树的实现
1. 二叉排序树的查找
从根结点开始比较,比根小向左查找,比根大向右查找。如果相等,则查找成功,返回当前结点的指针。若查找失败则返回nullptr。
// 查找结点
Node* Find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
2. 二叉排序树的插入
树为空,则直接新增节点,赋值给root指针。
树不空,按二叉搜索树性质查找插入位置,插入新节点。
插入时需要注意的是:
要添加一个记录cur的父结点,用于创建新结点后的连接。
连接新结点时,由于不知道新结点是父结点的左孩子还是右孩子,因此还要多加上一句判断。
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr; // 用于记录cur的父结点,方便后面进行连接
Node* cur = _root;
while (cur != nullptr)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(key);
// 连接新建结点与父结点
if (parent->_key > key)
parent->_left = cur;
else if (parent->_key < key)
parent->_right = cur;
return true;
}
3. 二叉排序树的删除
二叉排序树的删除比较复杂,因为如果删除的结点既有左孩子又有右孩子,为了保持二叉排序树的特性,不能随便找一个子树上的结点来替代删除的结点。
首先查找元素是否在二叉搜索树中,如果不存在,则返回,删除操作可能分为如下四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4种情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的左子树的最大结点或右子树的最小结点,用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除
// 删除结点
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur != nullptr)
{
// 开始找要删除的结点
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else //找到了要删除的结点
{
if (cur->_left == nullptr) // 当要删除的结点左子树为空时
{
if (cur == _root) // 如果删除的是根结点,直接让根指向右子树
{
_root = cur->_right;
}
else // 连接父结点和右子树
{
if (parent->_left == cur) parent->_left = cur->_right;
if (parent->_right == cur) parent->_right = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr) // 当要删除的结点右子树为空时
{
if (cur == _root) // 如果删除的是根结点,直接让根指向左子树
{
_root = cur->_right;
}
else // 连接父结点和左子树
{
if (parent->_left == cur) parent->_left = cur->_left;
if (parent->_right == cur) parent->_right = cur->_left;
}
delete cur;
}
else // 当删除的结点左子树和右子树都存在,可以找左子树中最右的结点
// 或者右子树中最左的结点来替代
{
Node* maxLeft = cur->_left;
Node* pmaxLeft = cur; // 记录左子树最右结点的父结点
while (maxLeft->_right) // 寻找左子树最右结点
{
pmaxLeft = maxLeft;
maxLeft = maxLeft->_right;
}
cur->_key = maxLeft->_key; // 用最右结点的值替代cur的值
if (pmaxLeft->_left == maxLeft) pmaxLeft->_left = maxLeft