目录
[1.插入 Insert](#1.插入 Insert)
[2.Erase 删除结点(难点)](#2.Erase 删除结点(难点))
二叉搜索树概念
其又称二叉排序树、二叉查找树。
满足以下性质:
非空左子树的key值小于根结点的key值;
非空右子树的key值大于根结点的key值;
左、右子树都是二叉搜索树。
二叉树搜索树的模拟实现
1.插入 Insert
在树中,寻找适合key插入的位置,找到插入返回true ,已有重复key返回false.
思路:
比较key 和根结点的_key 如果key大于_key 往根的右子树去找
如果key小于_key 往根的左子树去找
如果树中不存在已有的key 则必定走到空结点 ,链接上key的值即可
如果树中存在key 结束查找 返回false
画图辅助理解
在树中,插入key为五的结点:
定义一个cur临时结点
1--比较cur->_key 与key(下面简称比较) key小于cur->_key 往左子树中查找 cur=cur->left
2--比较 key>cur->_key 往右子树查找 cur=cur->right
3--比较 key<cur->_key 往左子树查找 cur=cur->left
4--比较 key>cur->_key 往右子树查找 cur=cur->right
5--cur为空 找到可以链接的位置 链接上结点
以下有俩点需要注意:
1.如果树为空 ,则需要先创建树
2.链接结点,需要保存cur的父亲
下面代码演示
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//找到适合插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur; //更新parent
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key) //更新parent
{
cur = cur->_right;
}
else
return false;
}
//准备插入 插入点一定是空结点
cur = new Node(key);
//判断插入父亲的左还是右
if (parent->_key > key) parent->_left = cur;
else parent->_right = cur;
//插入成功
return true;
}
2.Erase 删除结点(难点)
关于二叉搜索树的删除相对不容易,需要考虑多种情况,下面就来详细解释。
上图列举四种具有代表性的删除结点
分别是:
key结点的左和右子树都为空 key的左子树为空 key的右子树为空 key的左和右子树都不为空
对于左子树右子树都为空 我们可以归于左子树或者右子树为空的一种中
下文 只需要对 2 3 4三种方式进行剖析
这三种方式都必须先找到cur
找cur的方式与inser相同 ,在此不做介绍
1)cur的左为空
--1--判断cur是否为根结点 是则将cur->left赋值给_root
--2--判断cur是父亲的左还是右 ,链接cur->right
2)cur的右为空
该方式与1)类似
需要判断cur是否为root结点
不为root则需要判断是父亲的左还是右结点
在此不做过多的介绍
3)cur的左和右子树都不为空
如果我们简单的删除cur ,则破坏搜索二叉树,因此我们引入替换法
在cur的子树中,找到一个合适的值替换,使树仍为搜索二叉树
找替换结点的方式:
左子树中,找最大
右子树中,找最小
如果我们要删除key为3的结点 因为cur的结点有左子树和右子树 所以需要找替换
我们找cur右子树的最小值 即右子树最左边的元素 4
交换 3 和 4的值 然后删除 rightmin的结点
要删除rightmin结点 需要注意该结点的右子树需要被链接到cur的父亲上
代码剖析
bool Erase(const K& key)
{
//1.k的左为空
//2.k的右为空
//3.k左右都存在,替换删除
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur; //更新父亲
if (cur->_key < key) cur = cur->_right;
else if (cur->_key > key) cur = cur->_left;
//找到了
else
{
//1.左为空
if (cur->_left == nullptr)
{
//删除点为根
if (cur == _root) _root = cur->_right;
//判断是父亲的左、右链接
if (parent->_right == cur) //右链接
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
//2.右为空
else if (cur->_right == nullptr)
{
//删除点为根
if (cur == _root) _root = cur->_left;
//判断是父亲的左、右链接
if (parent->_right == cur) //右链接
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
//3.替换
else
{
//找右数的最小 右子树的最左边元素
Node* pminright = cur;
Node* minright = cur->_right;
//找到左树空就找到了
while (minright->_left)
{
pminright = minright; //保存父亲结点
minright = minright->_right;
}
cur->_key = minright->_key; //交换
//判断是父亲的左子树还是右子树要被删除
if (pminright->_left ==minright )
pminright->_left = minright->_right;
else
pminright->_right= minright->_right;
}
}
}
delete(cur);
return true;
}
3.InOder (中序遍历)
规则:左子树 ------根结点------右子树
遍历顺序 1 3 4 6 7 8 10 13 14
搜索二叉树的中序遍历可以将key按照大小遍历出
对于一个类中,我们调用InOder函数 是无法传递私有变量_root
因为写一个子函数提供遍历
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_val << endl;
_InOrder(root->_right);
}
4.Find
查找树中是否存在值为key的函数,找到返回结点指针,找不到返回空
思路是从根结点开始 ,如果key 小于cur->_key 那么往左子树查找,大于则往右子树查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
cur = cur->_right;
else if (cur->_key > key)
cur = cur->_left;
else
return cur;
}
return nullptr; //没找到,返回空指针
}
递归实现方式
关于递归的实现,其思路较简单 方法与循坏类似
写代码有一条不成文的准则:
能用循坏,尽量不用递归,避免栈溢出;
对于二叉搜索树的递归 需要注意:
--1--递归要传入结点参数,要写子函数调用
--2--cur的链接,不需要保存父亲结点 引用cur的指针
完整代码
展示key_value模型应用的代码
#include<iostream>
#include<string>
using namespace std;
namespace key_val
{
template<class K, class V> //key和value
//node
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _val;
BSTreeNode(const K& key, const V& val)
:_left(nullptr),
_right(nullptr),
_key(key),
_val(val)
{}
};
template<class K,class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
//找到适合插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur; //更新parent
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key) //更新parent
{
cur = cur->_right;
}
else
return false;
}
//准备插入 插入点一定是空结点
cur = new Node(key, value);
//判断插入父亲的左还是右
if (parent->_key > key) parent->_left = cur;
else parent->_right = cur;
//插入成功
return true;
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
cur = cur->_right;
else if (cur->_key > key)
cur = cur->_left;
else
return cur;
}
return nullptr; //没找到,返回空指针
}
bool Erase(const K& key)
{
//1.k的左为空
//2.k的右为空
//3.k左右都存在,替换删除
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur; //更新父亲
if (cur->_key < key) cur = cur->_right;
else if (cur->_key > key) cur = cur->_left;
//找到了
else
{
//1.左为空
if (cur->_left == nullptr)
{
//删除点为根
if (cur == _root) _root = cur->_right;
//判断是父亲的左、右链接
if (parent->_right == cur) //右链接
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
//2.右为空
else if (cur->_right == nullptr)
{
//删除点为根
if (cur == _root) _root = cur->_left;
//判断是父亲的左、右链接
if (parent->_right == cur) //右链接
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
//3.替换
else
{
//找右数的最小 右子树的最左边元素
Node* pminright = cur;
Node* minright = cur->_right;
//找到左树空就找到了
while (minright->_left)
{
pminright = minright; //保存父亲结点
minright = minright->_right;
}
cur->_key = minright->_key; //交换
//判断是父亲的左子树还是右子树要被删除
if (pminright->_left ==minright )
pminright->_left = minright->_right;
else
pminright->_right= minright->_right;
}
}
}
delete(cur);
return true;
}
void InOrder()
{
_InOrder(_root);
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_val << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
总结
本文重点学习搜索二叉树的插入和删除,它具有快速查找的功能,在查找具有O(logN)-O(N)的效率,关于搜索二叉树的实现,有较多的细节,也要仔细甄别。