一、搜索二叉树概念
搜索二叉树是一种树形结构,常用于map当中。搜索二叉树严格遵守左小右大的规则
C语言中实现搜索二叉树有一些困难,并且在面对一些特定题目实现较困难。因此采用C++的方式再次实现搜索二叉树
二、搜索二叉树的实现
插入
搜索二叉树在插入之前必须要先遍历查找到合适的位置,再找到合适的位置后将结点插入。这里需要考虑如何将插入节点与原二叉树连接,因此需要同时记录父节点以方便连接
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//将插入节点与树连接
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
删除
删除需要考虑的情况比插入更复杂:
①在叶子节点删除
下图中4、6、15都是叶子节点,删除叶子节点的处理比较简单,直接删除即可
②删除有左孩子或者有右孩子的节点
寻找删除节点的父节点,继承删除节点的子节点
这里需要考虑删除根节点的问题,如果根节点只有单一子树,由于根节点并没有父节点,会出现空指针问题
因此如果删除节点为根节点,并且根节点只有单一子树,则更新根节点(采用替代法也可以,但是更新根节点更简单)
③删除同时有左孩子和右孩子的节点
只能间接删除,同时有两个孩子,就只能采用替代法。选择删除节点左子树上的最大节点或者右子树上的最小节点替代该节点,因为需要保持左小右大的规则
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if(cur->_left==nullptr)//单个孩子左为空
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right==nullptr)//单个孩子右为空
{
if (cur == _root)
{
_root = cur->_left;
//如果删除根节点,由于根节点没有父节点,因此直接更新根节点
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else//同时有两个孩子
{
//找要删除节点的右树最小节点或左树最大节点替代
Node* minRight = cur->_right;//右树最小
Node* pminRight = cur;//minRight的父节点
//这里不赋值为nullptr也是考虑到如果删除根节点的空指针问题
while (minRight->_left)//最小需要往左找
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;//替代
//pminRight->_left = minRight->_right;对于根节点会有空指针问题
if (pminRight->_left == minRight)//考虑根节点删除问题
{
pminRight->_left = minRight->_right;
}
else
{
pminRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
整体实现
#pragma once
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _right;
BSTreeNode<K>* _left;
K _key;
BSTreeNode(const K& key)
:_left(nullptr),_right(nullptr),_key(key)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree() = default;//强制生成默认构造
BSTree(const BSTree<K>& t)//拷贝构造
{
_root = Copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)//赋值构造
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destory(_root);
}
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
//将插入节点与树连接
cur = new Node(key);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
{
cur = cur->_left;
}
else if (cur->_key < key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
if(cur->_left==nullptr)//单个孩子左为空
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right==nullptr)//单个孩子右为空
{
if (cur == _root)
{
_root = cur->_left;
//如果删除根节点,由于根节点没有父节点,因此直接更新根节点
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else//同时有两个孩子
{
//找要删除节点的右树最小节点或左树最大节点替代
Node* minRight = cur->_right;//右树最小
Node* pminRight = cur;//minRight的父节点
//这里不赋值为nullptr也是考虑到如果删除根节点的空指针问题
while (minRight->_left)//最小需要往左找
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;//替代
//pminRight->_left = minRight->_right;对于根节点会有空指针问题
if (pminRight->_left == minRight)//考虑根节点删除问题
{
pminRight->_left = minRight->_right;
}
else
{
pminRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
//递归实现封装接口
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
void InOrder()//二次封装,方便调用
{
_InOrder(_root);
cout << endl;
}
protected://递归实现
void Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
Node* Copy(Node*& root)
{
if (root == nullptr)
{
return nullptr;
}
Node* newroot = new Node(root->_key);
newroot->_left = Copy(root->_left);
newroot->_right = Copy(root->_right);
return newroot;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key = key)
{
return true;
}
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else
return _FindR(root->_left, key);
}
bool _InsertR(Node*& root, const K& key)
//当走到空节点时,root是上一次调用的引用(root->_right或root->_left的引用)
{
if (root == nullptr)
{
root = new Node(key);
//此时的root节点本就在树中,只不过为空。因此可以直接创建新节点而无需连接
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else
{
return false;
}
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _EraseR(root->_right,key);
}
else if (root->_key > key)
{
return _EraseR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* maxleft = root->_left;
while (maxleft->_right)
{
maxleft = maxleft->_right;
}
swap(root->_key , maxleft->_key);
return _EraseR(_root->_left, key);
//转换为子树删除,因为一定是右为空
//这里不能是maxleft因为是引用传参
}
delete del;
return true;
}
}
private:
Node* _root = nullptr;
};
搜索二叉树的应用
1.key模型
K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
实际应用:车库比对车牌号<num>,人脸识别<face>
2.key value模型
每一个关键码key,都有与之对应的值Value,即<key,value>的键值对
实际应用:英汉词典中英相互对应<word,Chinese>、学生姓名与学号相互对应<name,num>
#pragma once
//搜索二叉树的key,value模型
#include<iostream>
using namespace std;
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
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* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
// 链接
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = 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)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
// 删除
// 1、左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
} // 2、右为空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else
{
// 找右树最小节点替代,也可以是左树最大节点替代
Node* pminRight = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
pminRight = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;
if (pminRight->_left == minRight)
{
pminRight->_left = minRight->_right;
}
else
{
pminRight->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
}
protected:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value<<" ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
搜索二叉树性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多
最优情况 | 最坏情况 | |
树的形态 | 搜索二叉树接近完全二叉树 | 搜索二叉树为单边树 |
时间复杂度 |
由于搜索二叉树的形态不确定,导致其时间复杂度也取决于二叉树的形态(比如单边树)因此出现了平衡搜索二叉树,与搜索二叉树的不同就在于平衡。始终将二叉树调整到接近完全二叉树,使得时间复杂度为最优解