目录
- C++ 搜索二叉树
- 一. 介绍
- 二.简单实现搜索二叉树
- 1. 基本框架
- 2. 插入节点
- a. 图示:
- b. 递归实现:
- c. 非递归:
- 3. 删除节点
- a. 图示:
- b. 递归实现:
- c. 非递归:
- 三. 小结
C++ 搜索二叉树
又名:二叉搜索树、二叉排序树、二叉查找树等
一. 介绍
搜索二叉树又称二叉搜索树(Binary search tree),其具有以下性质:
- 若左子树不为空,根节点的值大于其左子树所有节点的值
- 若右子树不为空,根节点的值小于其右子树所有节点的值
- 对于左右子树也符合上述两条规则
示例三图,都符合搜索二叉树的条件,左孩子 < 根 < 右孩子。
三个树的形状不同,与其构建顺序有关。
但是可以发现它们中序遍历的结果都是升序的0 1 2 3 4
二.简单实现搜索二叉树
1. 基本框架
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& key = K())
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
private:
Node* _root = nullptr;
};
_root:指向一个搜索二叉树的根节点
2. 插入节点
a. 图示:
b. 递归实现:
注意:root为引用
- root为空,让root指向新建节点
- root不为空,如果新节点的值大于root的值,那么新节点肯定在root的右数中…依次类推,直到找到空位置处,即新节点位置
- 若新建的值存在,则不用再插入了,插入失败。
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool _InsertR(Node*& root, const K& key)
{
//根为空时
if (nullptr == root)
{
//*root为引用,修改会改变其父节点的左/右指针
root = new Node(key);
return true;
}
//根节点值小于插入的值
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
//根节点值大于插入的值
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else//如果存在该值,退出
{
return false;
}
}
c. 非递归:
-
树为空,新节点为根节点
-
树不为空,按搜索二叉树的性质找到新节点链接的对应位置。
遍历搜索二叉树,如果新节点大于当前节点,那么新节点应该在当前节点的右子树中…依次类推,直到当前节点为空时,即应该插入到该位置。可以记录当前节点的前父节点,因为父节点可能左右子树都为空,因此需要再做一次判断,插入到该父节点的正确位置
-
若插入的值已经存在,不用再插入了,插入失败。
bool Insert(const K& key)
{
Node* newNode = new Node(key);
//如果是第一个插入的根节点
if (_root == nullptr)
{
_root = newNode;//让_root指向根节点
}
else
{
Node* prev = nullptr;
Node* cur = _root;
//cur为nullptr即找到合适的位置
while (cur)
{
prev = cur;
//如果当前节点的值大于要插入的值
if (cur->_key > newNode->_key)
{
//当前节点向左移动
cur = cur->_left;
}
//如果当前节点的值小于要插入的值
else if (cur->_key < newNode->_key)
{
//当前节点向右移动
cur = cur->_right;
}
//如果当前节点的值等于要插入的值
else
{
return false; //不进行插入
}
}
//判断新节点是插入在prev的左还是右
if (prev->_key < newNode->_key)
{
prev->_right = newNode;
}
else
{
prev->_left = newNode;
}
}
return true;
}
3. 删除节点
a. 图示:
- 所删除节点的右子树为空节点
- 所删除节点的左子树为空节点
- 所删除节点的左右子树都为空,会在前两种情况中处理
- 删除节点的左右子树都不为空
方法1:同其右子树最小值的节点交换,然后再删除(右子树最小值的节点,其左子树必为空)
方法2:同其左子树最大值的节点交换,然后再删除(左子树最大值的节点,其右子树必为空)
b. 递归实现:
注意:root为引用
- 如果root为空,则无要删除的节点,返回false
- 如果root的值大于要删除节点的值,这要删除的节点一定在root的右子树中…依次寻找
- 如果root的值等于要删除节点的值,则需要删除该root
- root左子树为nullptr,则将root的右子树链接给root,
- root右子树为nullptr,则将root的左子树链接给root,
- root左右子树都不为nullptr,则可以找到左子树的最大值(或右子树的最小值),与root内容交互,然后删除左子树的最大值
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
//如果root为空
if (nullptr == root)
{
return false;
}
//root节点值小于删除节点的值
if (root->_key < key)
{
return _EraseR(root->_right, key);
}
//root节点值大于删除节点的值
else if (root->_key > key)
{
return _EraseR(root->_left, key);
}
else//root节点值等于删除节点的值
{
Node* del = root;
//如果删除的节点左子树为空
if (root->_left == nullptr)
{
//*root为引用,修改会改变其父节点的左/右指针
root = root->_right;
delete del;
}
//如果删除的节点右子树为空
else if (root->_right == nullptr)
{
//*root为引用,修改会改变其父节点的左/右指针
root = root->_left;
delete del;
}
//如果删除的节点左右子树都不为空
else
{
//找到左子树中最大值节点,即左子树的最右节点
Node* LMax = root->_left;
while (LMax->_right)
{
LMax = LMax->_right;
}
//将左子树中最大值节点和root值进行交换
std::swap(root->_key, LMax->_key);
//删除该左子树中最大值节点
return _EraseR(root->_left, key);
}
return true;
}
}
c. 非递归:
- 按搜索二叉树的规律,找到需要删除的节点cur
- 如果cur为nullptr,无该要删除的那个节点
- 如果cur不为空,对cur进行删除
- cur左子树为nullptr,则将root的右子树链接给root,
- cur右子树为nullptr,则将root的左子树链接给root,
- cur左右子树都不为nullptr,则可以找到左子树的最大值(或右子树的最小值),与cur内容交互,然后删除左子树的最大值
bool Erase(const K& key)
{
Node* prev = nullptr;
Node* cur = _root;
//找到要删除的节点
while (cur)
{
if (cur->_key == key)
{
break;
}
prev = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else
{
cur = cur->_left;
}
}
if (cur == nullptr)
{
return false;
}
//所删除节点的左子树为空
if (cur->_left == nullptr)
{
//将cur的右子树链接到父节点
if (_root == cur)//如果是删除根节点
//if(prev == nullptr)也可以写这个
{
_root = cur->_right;
}
else if (prev->_left == cur)//如果cur是prev的左孩子
{
prev->_left = cur->_right;
}
else//如果cur是prev的右孩子
{
prev->_right = cur->_right;
}
delete cur;//删除节点
}
//右子树为空
else if (cur->_right == nullptr)
{
//将cur的左子树链接到父节点
if (_root == cur)//如果是删除根节点
{
_root = cur->_left;
}
else if (prev->_left == cur)//链接到父节点的左边
{
prev->_left = cur->_left;
}
else链接到父节点的右边
{
prev->_right = cur->_left;
}
delete cur;
}
else //左右都不为空
{
Node* curLTM = cur->_left; //cur的left子tree的max结点
Node* prevLTM = cur;//记录curLTM的父节点
//找到左子树中最大值节点,即左子树的最右节点
while (curLTM->_right)
{
prevLTM = curLTM;
curLTM = curLTM->_right;
}
cur->_key = curLTM->_key;//交换值
//交换后,则需要删除curLTM
//将curLTM的右子树给其父节点
if (prevLTM->_left == curLTM)
{
prevLTM->_left = curLTM->_left;
}
else
{
prevLTM->_right = curLTM->_left;
}
delete curLTM;//删除curLTM
}
return true;
}
三. 小结
搜索二叉树的插入、删除,不同于普通二叉树,主要是要保持特殊规则(左孩子 < 根 < 右孩子)。在实现过程中,如删除一个左右子树都不为空的节点时,实际上是通过操作来转化为删除一个左子树或右子树为空的节点。当然还有许多细节,建议边画图,边实现。
对于搜索二叉树,顾名思义,其特长在搜索(查找)功能上。一般情况下,其查找的时间复杂度为树的高度, O ( H ) O(H) O(H)。在此基础上,一棵搜索二叉树最优的情况应该类似于完全二叉树( H ≈ l o g 2 N H\approx log_2N H≈log2N),最坏的情况则是单支树( H = N H = N H=N)。
对于这种情况,有大佬对二叉搜索树进行优化,产生出AVL树,红黑树等结构。
🦀🦀观看~