"Oh hey New Wrold!'
(1)搜索二叉树:
①什么是搜索二叉树:
搜索二叉树,又称为二叉排序树。具有二叉树性质。
如若它的左子树不为空,那么左子树所有的节点小于根节点。
如若它的右子树不为空,那么右子树所有的节点大于根节点。
②搜索二叉树查找
(2)搜索二叉树的模拟实现:
①结构:
namespace dy
{
//树的节点
template<class K>
struct TreeNode
{
TreeNode<K>* _left;
TreeNode<K>* _right;
K _key;
TreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
//搜索二叉树
template<class K>
class BSTree
{
typedef TreeNode<K> TNode;
public:
BSTree()
:_root(nullptr)
{}
private:
TNode* _root; //根节点
};
}
②树的插入:
非递归版本:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new TNode(key);
}
//cur记录插入的位置
TNode* cur = _root;
//parent为父节点 记录要连接的位置
TNode* parent = nullptr;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else //相等 既 已经插入过key值了
{
return false;
}
}
//此时cur 为空
cur = new TNode(key);
//链接
if (key > parent->_key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
递归版本:
递归版本的思想极其巧妙。
//非递归
//和中序 一样 用到递归 就需要借用子函数去执行
bool InsertR(const K& key)
{
if (_root == nullptr)
{
_root = new TNode(key);
return true;
}
else
{
return _InsertR(_root, key);
}
}
bool _InsertR(TNode*& root, const K& key)
{
//root为空的时候 创建并插入节点
if (root == nullptr)
{
//root 实质上是 上一个parent节点的NULL
root = new TNode(key);
return true;
}
if (key > root->_key)
{
//每一种情况进行返回
return _InsertR(root->_right, key);
}
else if (key < root->_key)
{
//每一种情况进行返回
return _InsertR(root->_left, key);
}
return false;
}
③中序打印:
我们先来插入一些数值,试试。 需要注意的规律,搜索二叉树走中序,也就会成为顺序。
private:
//当然我们也需要 让子函数 成为私有 仅供成员内部访问
void InOrder(TNode* root)
{
if (root == nullptr)
return;
//递归先走左 树 再走右树
InOrder(root->_left);
cout << root->_key << endl;
InOrder(root->_right);
}
void InOrder()
{
if (_root == nullptr)
return;
else
_InOrder(_root);
}
那么我们的插入也就没有问题了。
④查找:
找到树的节点返回,否则返回nullptr.
非递归版本:
//查找
TNode* Find(const K& key)
{
TNode* cur = _root;
while (cur)
{
if (key > _root->_key)
{
cur = cur->_right;
}
else if (key < _root->_key)
{
cur = cur->_left;
}
}
return cur;
}
递归版本:
//查找
TNode* _FindR(TNode* root, const K& key)
{
if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (key < root->_key)
{
return _FindR(root->_left, key);
}
return root;
}
查找的思想就很简单了,弄懂 上面的插入 这里的代码理解起来不是很难。
⑤删除:
搜索二叉树 的删除 相较于上面功能 实现起来较为复杂。
非递归:
删除单一节点:
删除父节点:
上面的是一种传统写法:
我们对于MinRight的删除 可以采用 复用的方式。
我们删除的功能似乎完成得很好。
但如何有效地检验自己代码是否合格,最直接办法就是,删完整个树。
此时 代码跑死了。
测试:
我们很快反应 过来,就是删除到最后一个节点的时候会出问题。
//删除
bool Erase(const K& key)
{
//让cur 作为被删除的点
TNode* cur = _root;
TNode* parent =nullptr;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//开始删除
//其实第一种和第二种 情况 可以合并 因为都是让
//cur的父节点指向 自己子节点(nullptr这种情况可以勉强认为是)
//需要被认领的是 右子节点
if (cur->_left == nullptr)
{
//root为最后一个
if (cur == _root)
{
_root = cur->_left;
}
else
{
//cur在parent的那边
if (parent->_left == cur)
{
//在左边就用左边去 链接 孩子
parent->_left = cur->_right;
}
else
{
//在右边 就用右支 认领 孩子
parent->_right = cur->_right;
}
}
//记到删除
delete cur;
}
else if (cur->_right == nullptr) //cur的左节点需要被认领
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
//cur 在 父节点的 左边
if (parent->_left == cur)
{
//左边去认领
parent->_left = cur->_left;
}
else
{
//否则就 用右边去认领
parent->_right = cur->_left;
}
}
delete cur;
}
else
{
/*删除父亲节点
替换法 首先找到能替换父节点的值 这里我们统一找大的那个值
也就是 右子树最小值
去走cur 的右支*/
//TNode* MinRight = cur->_right;
右子树最小值的父节点
//TNode* MinParent = cur;
//while (MinRight->_left) //注意这类不能用MinRight 作为判断条件否则 会走到空
//{
// MinParent = MinRight;
// MinRight = MinRight->_left;
//}
此时MinRight为要替换的节点
并保存值
//K Min = MinRight->_key;
传统写法
删除更换的节点
//if (MinParent->_left == MinRight)
//{
// MinParent->_left = MinRight->_right;
//}
//else //排除 MinRight 不在cur 的左支
//{
// MinParent->_right = MinRight->_right;
//}
//cur->_key = Min;
//delete MinRight;
//复用
TNode* MinRight = cur->_right;
while (MinRight->_left)
{
MinRight = MinRight->_left;
}
//找到MinRight
K min = MinRight->_key;
//删除
this->Erase(min);
cur->_key = min;
}
return true;
}
}
return false;
}
递归版本:
//删除
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(TNode*& root, const K& key)
{
//Erase 递归删除 仍然可以 借用引用
if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if(key < root->_key)
{
return _EraseR(root->_left, key);
}
else
{
//相等
//这里还是两者情况
if (root->_left == nullptr)
{
//先保存到这个root点
TNode* del = root;
//直接让root 点 链接到下一个 跳过了要删除的root
root = root->_right;
delete del;
}
else if (root->_right == nullptr)\
{
TNode* del = root;
root = root->_left;
delete del;
}
else
{
//删除双节点
//这里就不能 用替换法 删除
TNode* MinRight = root->_right;
while (MinRight->_left)
{
MinRight = MinRight->_left;
}
//找到准备替换的值
K min = MinRight->_key;
//调用自己 去删除 min 的节点
this->_EraseR(root->_right, min);
//注意这里 的root 是没有动的 一直用的是引用!!
root->_key = min;
}
return true;
}
return false;
}
整个搜索二叉树,就只有删除难度稍大。
⑥拷贝、赋值、析构成员函数:
很显然,如果仅凭 编译器提供的拷贝、赋值 只是完成值拷贝。
所以需要我们去手段写。
//拷贝构造
//t2(t1)
BSTree(const BSTree<K>& t)
{
//我们需要去递归 new和t一样的 节点
_root = _Copy(t._root);
}
TNode* _Copy(TNode* root)
{
if (root == nullptr)
return nullptr;
//每个拷贝创建的节点
TNode* copyroot = new TNode(root->_key);
//去构建他的左
copyroot->_left = _Copy(root->_left);
//右
copyroot->_right = _Copy(root->_right);
return copyroot;
}
//赋值运算
BSTree<K>& operator=(BSTree<K>& t)
{
if (this != &t)
{
swap(_root, t._root);
}
return *this;
}
//析构
~BSTree()
{
//这里交给 这个子函数去删除节点
_Destory(_root);
_root = nullptr;
}
void _Destory(TNode* root)
{
if (root == nullptr)
return;
_Destory(root->_left);
_Destory(root->_right);
delete root;
}
(3)搜索二叉树实际应用:
①性能分析:
可能对搜索二叉树的第一印象,就是查找数据的时间复杂度为高度次O(logN);
但在极端情况下,并不是。而是O(N);
只有在完全二叉树或者满二叉树的条件下,可以达到O(logN)
②适用场景;
搜索二叉树 顾名思义,用于查找比较方便。在这基础上 延伸出来 KV树;
我们可以用来查阅什么东西,比如说字典:
template<class K,class V>
struct TreeNode
{
TreeNode<K,V>* _left;
TreeNode<K,V>* _right;
K _key;
V _val;
TreeNode(const K& key,const V& val)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_val(val)
{}
};
template<class K,class V>
class BSTree
{
typedef TreeNode<K, V> TNode;
public:
BSTree()
:_root(nullptr)
{}
bool _InsertR(TNode*& root, const K& key,const V& val)
{
//root为空的时候 创建并插入节点
if (root == nullptr)
{
//root 实质上是 上一个parent节点的NULL
root = new TNode(key,val);
return true;
}
if (key > root->_key)
{
//每一种情况进行返回
return _InsertR(root->_right, key,val);
}
else if (key < root->_key)
{
//每一种情况进行返回
return _InsertR(root->_left, key,val);
}
return false;
}
bool InsertR(const K& key,const V& val)
{
if (_root == nullptr)
{
_root = new TNode(key,val);
return true;
}
else
{
return _InsertR(_root, key,val);
}
}
void _InOrder(TNode* root)
{
if (root == nullptr)
return;
//递归先走左 树 再走右树
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//查找
TNode* _FindR(TNode* root, const K& key)
{
if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (key < root->_key)
{
return _FindR(root->_left, key);
}
return root;
}
TNode* FindR(const K& key)
{
return _FindR(_root, key);
}
private:
TNode* _root;
};
我们对节点内的 模板参数设置增加一个,用于存另外的值。
统计个数:
搜索二叉树的讲解就到这里 了。
感谢你的阅读。
祝你好运~