文章目录
二叉搜索树
二叉搜索树的概念
二叉搜索树又称二叉排序树(因为走中序,数据都是有序的),它或者是一棵空树,或者是具有以下性质的二叉树。
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
即每颗子树中,左子树中值都小于根,右子树中的值都大于根。
如,int a[]={5,3,4,1,7,8,2,6,0,9};
其时间复杂度极端情况下是 O ( n ) O(n) O(n)。
只有当树的形状接近完全二叉树或者满二叉树,才能达到 l o g ( n ) log(n) log(n)。
所以实际中搜索二叉树极端情况下没办法保证效率,所以需要对搜索二叉树进一步探索特性拓展延伸:AVLTree 红黑树
他们对搜索二叉树左右高度提出了要求,非常接近完全二叉树,他们效率可以达到 O ( n ) O(n) O(n)。
上述一般用于内存中查找,当数据在磁盘中时(外查找),对树高度进一步提出了要求,进一步衍生出了B树和B+树,B树系列属于多叉平衡树。
增删查改的时间复杂度 O ( n ) O(n) O(n)。
二叉搜索树操作
定义
#include<iostream>
#include<string>
using namespace std;
template <typename K>
struct BSTreeNode
{
public:
BSTreeNode* _left;
BSTreeNode* _right;
BSTreeNode(K key)
:_left(nullptr),
_right(nullptr),
_key(key)
{ }
K _key;
};
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
private:
Node* _root;
};
非递归版
插入
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
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;
}
private:
Node* _root;
};
中序遍历
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
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;
}
void _InOrder(Node* root){
if(root == nullptr) return;
_InOrder(root->_left);
cout<<root->_key<<endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
private:
Node* _root;
};
int main()
{
BSTree<int>t;
int a[]={5,3,4,1,7,8,2,6,0,9};
for(auto e :a)
{
t.Insert(e);
}
t.InOrder();
return 0;
}
查找
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
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;
}
Node* Find(const K& key)
{
Node* cur =_root;
while(cur){
if( key >cur -> _key ) cur = cur -> _right;
else if( key < cur-> _key) cur = cur ->_left;
else return cur;
}
return nullptr;
}
void _InOrder(Node* root){
if(root == nullptr) return;
_InOrder(root->_left);
cout<<root->_key<<endl;
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root);
}
private:
Node* _root;
};
删除
删除一个值等于key
的节点,分情况分析。
- 要删除的是
6
,9
,0
…,非常好处理。特征:叶子节点。- 删除当前节点,将父亲对应的儿子节点置为野指针。
- 要删除的节点是
8
,1
…也还行。特征:只有一个孩子- 删除当前节点,把孩子交给当前节点的父亲,顶替自己的位置。
- 要删除的是
5
,7
…。特征:有两个孩子。- 解决方案:替换法删除。
- 去孩子里面找一个值能替换自己位置的节点,替换自己删除。
- 左子树的最大节点——左子树最右节点就是最大的
- 右子树的最小节点——右子树最左节点就是最小的
- 这两个节点去替换后,都符合特征1或者特征2,可以直接删除。
- 再分析一下,特征1的处理可以用特征2的做法来处理。因此只要处理特征2和特征3两种。
对于删除特征2的情况,要考虑下图的细节


非递归版本
:
注意删除到只有左子树全为空或者右子树全为空的特殊情况,father
是空的。
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
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;
}
Node* Find(const K& key)
{
Node* cur =_root;
while(cur){
if( key >cur -> _key ) cur = cur -> _right;
else if( key < cur-> _key) cur = cur ->_left;
else return cur;
}
return nullptr;
}
void _InOrder(Node* root){
if(root == nullptr) return;
_InOrder(root->_left);
cout<<root->_key<<endl;
_InOrder(root->_right);
}
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* minParent = cur;
Node* minRight = cur -> _right;
while(minRight -> _left) {minParent = minRight;minRight = minRight ->_left;}
cur->_key = minRight ->_key;//保存替换节点的值
//删除替换的节点
if(minParent -> _left == minRight){
minParent ->_left =minRight ->_right;
}
else minParent ->_right = minRight ->_right;
delete minRight;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
}
private:
Node* _root;
};
特征3
的递归版本
:
else{//左右都不为空,替换法删除
//假设规定右子树最小节点去替换
Node* minRight = cur -> _right;
while(minRight -> _left) minRight = minRight ->_left;
K mi =minRight ->_key;
//递归调用自己去删除替换节点,一定会走到左为空的情况处理
this->Erase(mi);
cur->_key=mi;
}
#include"BinarySearchTree.h"
int main()
{
BSTree<int>t;
int a[]={5,3,4,1,7,8,2,6,0,9};
for(auto e :a)
{
t.Insert(e);
}
t.InOrder();
t.Erase(8);
t.InOrder();
t.Erase(5);
t.InOrder();
for(auto e: a){
t.Erase(e);
t.Inorder();
}
return 0;
}
递归版本
一般递归版本的为了接口的简洁性,再套一层,这样在外部调用就比较符合封装。
如果递归版本细节没想清除就画函数递归展开图。
查找
_FindR的查找
:
private:
Node* _FindR(Node* root , const K& key)
{
if(root == nullptr ) return nullptr;
if(root -> _key < key) return _FindR(root-> _right ,key);
else if( root-> _key > key) return _FindR(root -> _left ,key);
else return root;
}
public:
Node* FindR(const K& key){
return _FindR( _root,key);
}
插入
有什么办法将插入的结点和父亲链接起来呢?
- 一个法子就是在函数参数里面再加一个
parent
- 或者在到递归边界的时候再使用
Find
查找父亲 - 把参数中的
Node* root
改成引用(比较好)
private:
bool _InsertR(Node*& root ,const K& key){
if( root == nullptr ){//插入
root = new Node(key);
return true;
}
if( root -> _key < key)
{
return _InsertR(root->_right);
}
else if( root -> _key > key){
return _InsertR(root->_left);
}
else return false;
}
public:
bool InsertR( const K& key){
return _InsertR(_root,key);
}
删除
递归版本
:
对于特征3来说,递归下去就可以找到特征1/2的情况,使用它们的处理方式,然后赋值。
由于传了引用,函数参数中就不是拷贝了,因此是想再原图上直接修改一样。
对于特征3,引用起不了作用。一种做法是使用非递归版中特征3的朴素做法。
private:
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 {
//特征1和特征2
if(root ->_left==nullptr )
{
Node* tmp = root;
root = root -> _right;
delete tmp;
return true;
}
else if(root ->_right == nullptr)
{
Node* tmp =root;
root = root ->_left;
delete tmp;
return true;
}
//特征3
else{
Node* minParent = root;
Node* minRight = root -> _right;
while(minRight -> _left) {minParent = minRight;minRight = minRight ->_left;}
root->_key = minRight ->_key;//保存替换节点的值
//删除替换的节点
if(minParent -> _left == minRight){
minParent ->_left =minRight ->_right;
}
else minParent ->_right = minRight ->_right;
delete minRight;
}
return true;
}
}
public:
bool EraseR(const K&key){
return _EraseR(root,key);
}
另一种方式就是使用非递归中的特征3的递归处理。
private:
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 {
//特征1和特征2
if(root ->_left==nullptr )
{
Node* tmp = root;
root = root -> _right;
delete tmp;
return true;
}
else if(root ->_right == nullptr)
{
Node* tmp =root;
root = root ->_left;
delete tmp;
return true;
}
//特征3
else{
Node* MinRight = root ->_right;
while(MinRight ->_left) MinRight= MinRight->_left;
K min = MinRight-> _key;
//转换成在root的右子树删除Min
_EraseR(root->right,min);
root-> _key =min;
}
return true;
}
}
public:
bool EraseR(const K&key){
return _EraseR(root,key);
}
类的默认成员函数
析构
#include<iostream>
#include<string>
using namespace std;
template <typename K>
struct BSTreeNode
{
public:
BSTreeNode* _left;
BSTreeNode* _right;
BSTreeNode(K key)
:_left(nullptr),
_right(nullptr),
_key(key)
{ }
K _key;
};
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
~BSTree(){
_Destroy(_root);
_root=nullptr;
}
private:
void _Destroy(Node* root){
if(root == nullptr) return;
if(root->_left ) _Destroy(root->_left);
if(root->_right) _Destroy(root->_right);
delete root;
}
private:
Node* _root;
};
拷贝构造
先拷贝根,再拷贝左子树,再拷贝右子树。
#include<iostream>
#include<string>
using namespace std;
template <typename K>
struct BSTreeNode
{
public:
BSTreeNode* _left;
BSTreeNode* _right;
BSTreeNode(K key)
:_left(nullptr),
_right(nullptr),
_key(key)
{ }
K _key;
};
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
~BSTree(){
_Destroy(_root);
_root=nullptr;
}
BSTree(const BSTree<K>& t){
_root = _Copy(t._root);
}
private:
void _Destroy(Node* root){
if(root == nullptr) return;
if(root->_left ) _Destroy(root->_left);
if(root->_right) _Destroy(root->_right);
delete root;
}
Node* _Copy(Node* root){
if(root == nullptr) return nullptr;
Node* newnode = new Node(root ->_key);
newnode->_left = _Copy( root ->_left);
newnode->_right = _Copy(root -> _right);
return newnode;
}
private:
Node* _root;
};
operator=赋值
operator=
赋值建议采用现代写法,借助拷贝构造去做。
#include<iostream>
#include<string>
using namespace std;
template <typename K>
struct BSTreeNode
{
public:
BSTreeNode* _left;
BSTreeNode* _right;
BSTreeNode(K key)
:_left(nullptr),
_right(nullptr),
_key(key)
{ }
K _key;
};
template <typename K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{ }
~BSTree(){
_Destroy(_root);
_root=nullptr;
}
BSTree(const BSTree<K>& t){
_root = _Copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root,t._root);
return *this;
}
private:
void _Destroy(Node* root){
if(root == nullptr) return;
if(root->_left ) _Destroy(root->_left);
if(root->_right) _Destroy(root->_right);
delete root;
}
Node* _Copy(Node* root){
if(root == nullptr) return nullptr;
Node* newnode = new Node(root ->_key);
newnode->_left = _Copy( root ->_left);
newnode->_right = _Copy(root -> _right);
return newnode;
}
private:
Node* _root;
};
二叉搜索树的应用
K模型
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
查找在不在的判断就是K模型
Key搜索场景:宿舍楼门禁,车库的扫描抬杆系统等。
比如宿舍楼门禁,可以将宿舍楼里面同学的学号都存储到BSTree<string>StuNumSet
比如车库的扫描抬杆系统,将小区业主车牌号录入系统,放到BSTree<string>CarNumSet
上述实现的代码就是K模型
的。
KV模型
KV——(key-value)
KV搜索场景:
- 高铁站,网上买票,刷身份证进站。买的每张票都关联了一个人的身份证号。刷身份证进站,机器读取到的是身份证号码,系统要通过身份证号找到身份证号关联的车票,再看票的日期和车次信息,匹配才开门。
- 简单中英互翻字典
- 统计一个文本中每个单词出现的次数
#include<iostream>
#include<string>
using namespace std;
namespace KV
{
template <typename K,typename V>
struct BSTreeNode
{
public:
BSTreeNode<K,V>* _left;
BSTreeNode<K,V>* _right;
BSTreeNode(const K& key,const V& value)
:_left(nullptr),
_right(nullptr),
_key(key),
_value(value)
{ }
K _key;
V _value;
};
template <typename K,typename V>
class BSTree
{
typedef BSTreeNode<K,V> Node;
public:
BSTree()
:_root(nullptr)
{ }
//涉及深浅拷贝,需要实现拷贝构造 operator = 等
~BSTree(){
_Destroy(_root);
_root=nullptr;
}
BSTree(const BSTree<K>& t){
_root = _Copy(t._root);
}
BSTree<K, V>& operator=(BSTree<K, V> t)
{
swap(_root, t._root);
return *this;
}
public:
bool EraseR(const K&key){
return _EraseR(root,key);
}
bool InsertR( const K& key,const V&value){
return _InsertR(_root,key,value);
}
Node* FindR(const K& key){
return _FindR( _root,key);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
bool _InsertR(Node*& root ,const K& key,const V& value){
if( root == nullptr ){//插入
root = new Node(key,value);
return true;
}
if( root -> _key < key)
{
return _InsertR(root->_right);
}
else if( root -> _key > key){
return _InsertR(root->_left);
}
else return false;
}
Node* _FindR(Node* root , const K& key)
{
if(root == nullptr ) return nullptr;
if(root -> _key < key) return _FindR(root-> _right ,key);
else if( root-> _key > key) return _FindR(root -> _left ,key);
else return root;
}
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 {
//特征1和特征2
if(root ->_left==nullptr )
{
Node* tmp = root;
root = root -> _right;
delete tmp;
return true;
}
else if(root ->_right == nullptr)
{
Node* tmp =root;
root = root ->_left;
delete tmp;
return true;
}
//特征3
else{
Node* MinRight = root ->_right;
while(MinRight ->_left) MinRight= MinRight->_left;
K min = MinRight-> _key;
V value = MinRight->_value;
//转换成在root的右子树删除Min
_EraseR(root->right,min);
root-> _key =min;
root->_value = value;
}
return true;
}
}
void _Destory(Node* root)
{
if (root == NULL)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
}
Node* _Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyNode = new Node(root->_key, root->_value);
copyNode->_left = _Copy(root->_left);
copyNode->_right = _Copy(root->_right);
return copyNode;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":"<<root->_value<<endl;
_InOrder(root->_right);
}
private:
Node* _root;
};
}
简单地测试一下简单字典的实例
void TestBSTree()
{
KV::BSTree<string,string> dict;
dict.InsertR("apple","苹果");
dict.InsertR("banana","香蕉");
dict.InsertR("candy","糖果");
dict.InsertR("duck","鸭子");
string str;
while(cin>>str){
KV::BSTreeNode<string,string> *ret = dict.FindR(str);
if(ret == nullptr) {
cout<<"单词拼写错误"<<endl;
}
else{
cout<<string<<" 中文翻译为 "<<ret->_value<<endl;
}
}
}
测试一下统计单词出现次数的实例
void TestBSTree()
{
string arr[]={"aaa","bb","cc","cc","bb","aaa","aaa","aaa","d"};
KV::BSTree<string,int> countTree;
for(const auto& str:arr)
{
//先查找在不在搜索树
//不在说明要插入
auto ret = countTree.FindR(str);
if(ret == nullptr){
coutTree.InsertR(str,1);
}
else{
ret->value ++;
}
}
}