二叉搜索树
一、非递归实现
1.二叉搜索树的概念
二叉搜索树又称二叉排序树,它可以是一颗空树,若不是空树,则它具有以下性质:
a.若它的左子树不为空,则左子树上的所有的值都小于根节点的值
b.若它的右子树不为空,则右子树上的所有的值都大于根节点的值
c.它的左右子树也分别为二叉搜索树,而且不存在数据冗余
判断下面三棵树是否为二叉搜索树
A树中以值为10的节点,它的右子树不满足二叉搜索树的概念,它右子树上的值小于根节点的值,所以不是二叉搜索树。
B、C两树都满足二叉搜索树的概念,所以它两都是二叉搜索树。
2.非递归实现(Insert 、Erase)
维护一颗二叉树要对它的节点进行管理,下面是对节点的描述:
//BinarySearchTreeNode
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
🌞二叉搜索树的插入:
1、当二叉搜索树是一颗空树时,直接把节点赋值给_root;
2、当二叉搜索树是非空树时:
a、当插入的值比当前节点的值小时,需要去当前节点的左子树去找合适的位子;
b、当插入的值比当前节点的值大时,需要去当前节点的右子树去找合适的位子;
c、重复a、b过程,直到找到合适的位子(过程中如果有冗余(一样)的值,插入失败)
如下为插入16和32的图像过程:
代码实现:
typedef BSTreeNode<K> Node;
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;
}
}
if (parent->_key < key)
{
parent->_right = new Node(key);
}
else
{
parent->_left = new Node(key);
}
return true;
}
🌞二叉搜索树的查找:
1、如果是空树,返回false,没有找到;
2、如果是非空树,进行查找:
a、如果查找的值比_root的值小时,在其左子树进行查找;
b、如果查找的值比_root的值大时,再其右子树进行查找;
c、重复a、b操作,直到找到为止(返回true)或没有找到为止(返回false);
假设查找33,把_root 赋值给 cur,因为33比cur节点的值大,所以要去右子树去找,cur=cur->_right;然后33又比当前cur的值小,所以要去它的左子树去找,cur=cur->_left;此时,33和当前cur的值相等,所以找到了,返回true。
typedef BSTreeNode<K> Node;
bool Find(const K& key)
{
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while(cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
最后分析一下二叉搜索树查找的时间复杂度:
在最坏的情况下,二叉搜索树可能是一条单边树,如下图:
对这颗二叉搜索树进行查找,需要找高度次,所以时间复杂度为O(N)。
🌞二叉搜索树的删除:
根据要删除的节点有几个孩子,把删除节点的方式分为两种:
1、直接删除:没有孩子或者只有一个孩子,直接删除该节点,把它的孩子托管给它的父亲;
a、被删除节点没有孩子;
b、被删除节点只有左孩子;
c、被删除节点只有右孩子;
其实可以把 a 归类于 b或者c中,假设把 a 归类于 b 中,它的孩子是一个空孩子;
图形演示(假设已经找到了要删除的节点):
假设要删除的节点为7,该节点没有孩子,但我们把它归类为b中,为了方便理解,把它的左孩子(空)画了出来,因为要删除节点是其父亲的右孩子,所以把要删除节点的左孩子链接给其父亲的右孩子。(可以试着删除一下14节点,画一下图像演示)
假设要删除的节点是根节点,它属于只有一个孩子或者没有孩子,此时的parent为nullptr,不能进行托管,那么我们判断一下cur 是否等于 _root,如果等于,直接把_root的非空孩子给_root;
代码实现(直接删除法):
if(cur->_left == nullptr)//只有右孩子
{
if(cur == _root)
{
_root = cur->_right;
}
else
{
if(cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right == nullptr)没有孩子或者只有左孩子
{
if(cur == nullptr)
{
_root = cur->_left;
}
else
{
if(cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
2、替代法删除:
a、要删除的节点有两个孩子,因为无法将两个孩子同时都托管给其父亲,所以我们需要找一个节点来替代它。那么可以找谁可以来替代它呢?可以找其左子树的最大节点,因该节点比左子树的所有值都大,比右子树的所有值都小;还可以找右子树的最小节点,因为该节点比右子树所有节点都小,比左子树的所有节点都大。(以找右子树的最小节点为例)
假设删除节点3,在右子树中找最小节点,最小节点的左子树为空;找到最小节点后把它与被删除节点交换,然删除Minright就可以了(直接删除法,没有左孩子,可能有右孩子,把有孩子托管给其父亲,即使右孩子为空)
代码实现(替代删除法):
else//左右孩子都不为空
{
Node* minParent = cur;
Node* Minright = cur->_right;
while(Minright->_left)
{
minParent = Minright;
Minright = Minright->_left;
}
swap(cur->_key,Minright->_key);
//因为右边找最小,所以找到的一定左子树为空
if(Minright == minParent->_left)
{
minParent->_left = Minright->_right;
}
else
{
minParent->_right = Minright->_right;
}
delete minRight;
删除的整体代码实现:
typedef BSTressNode<K> Node;
bool Erase(const K& key)
{
if(_root == nullptr)
{
return false;
}
Node* cur = _root;
Node* parent = nullptr;
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(cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right == nullptr)
{
if(cur == nullptr)
{
_root = cur->_left;
}
else
{
if(cur == parent->_left)
{
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;
}
swap(cur->_key,Minright->_key);
//因为右边找最小,所以找到的一定左子树为空
if(Minright == minParent->_left)
{
minParent->_left = Minright->_right;
}
else
{
minParent->_right = Minright->_right;
}
delete MinRight;
}
return true;
}
}
return false;
}
3、非递归实现二叉搜索树完整代码
1、代码实现
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
Node* CopyTree(Node* root)
{
if(root == nullptr)
{
return nullptr;
}
Node* CopyNode = new Node(root->_key);
CopyNode->_left = CopyTree(root->_left);
CopyNode->_right = CopyTree(root->_right);
return CopyNode;
}
void DestroyTree(Node* root)
{
if(root == nullptr)
{
return;
}
DestroyTree(root->_left);
DestroyTree(root->_right);
delete root;
}
public:
BSTree() = default;//c++11中强制编译器自己生成默认构造函数
BSTree(const BSTree<K>& t)
{
_root = CopyTree(t._root);
}
~BSTree()
{
DestroyTree(_root);
_root = nullptr;
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root,t._root);
return *this;
}
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;
}
}
if (parent->_key < key)
{
parent->_right = new Node(key);
}
else
{
parent->_left = new Node(key);
}
return true;
}
bool Find(const K& key)
{
if (_root == nullptr)
{
return false;
}
Node* cur = _root;
while(cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
bool Erase(const K& key)
{
if(_root == nullptr)
{
return false;
}
Node* cur = _root;
Node* parent = nullptr;
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(cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}
else if(cur->_right == nullptr)//没有孩子或者只有左孩子
{
if(cur == nullptr)
{
_root = cur->_left;
}
else
{
if(cur == parent->_left)
{
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;
}
swap(cur->_key,Minright->_key);
//因为右边找最小,所以找到的一定左子树为空
if(Minright == minParent->_left)
{
minParent->_left = Minright->_right;
}
else
{
minParent->_right = Minright->_right;
}
delete MinRight;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
二、递归实现
1、递归实现(Insert、Erase)
🌞二叉搜索树递归插入:
1、如果是一颗空树,因为是引用传参,root就是_root的别名,new一个Node给root也就是给_root;
2、如果是一颗非空树,引用传参;
a、当root->_key < key时,就递归插入它的右子树_EraseR(root->_right,key);
b、当root->_key > key时,就递归插入它的左子树_EraseR(root->_left,key);
c、当root->_key == key时,插入失败,return false;
图形演示:
代码实现:
bool InsertR(const K& key)
{
return _InsertR(_root,key);
}
bool _InsertR(Node*& root,const K& key)
{
if(root == nullptr)
{
root = new Node(key);
return true;
}
if(root->_key < key)
{
_InsertR(root->_right,key);
}
else if(root->_key > key)
{
_InsertR(root->_left,key);
}
else
{
return false;
}
}
🌞二叉搜索树递归查找:
bool FindR(const K& key)
{
return _FindR(_root,key);
}
bool _FindR(const Node* root,const K& key)
{
if(root == nullptr)
{
return false;
}
if(root->_key < key)
{
_FindR(root->_right,key);
}
else if(root->_key > key)
{
_FindR(root->_left,key);
}
else
{
return true;
}
}
🌞二叉搜索树删除递归:
1、如果是一颗空树,删除失败,返回false;
2、如果是一颗非空树,引用传参;
a、当root->_key < key时,就递归删除它的右子树_EraseR(root->_right,key);
b、当root->_key > key时,就递归删除它的左子树_EraseR(root->_left,key);
c、当root->_key == key时,就可以进行删除了,删除方法和非递归删除一样;
因为是引用传参,所以root 就是 其父亲的一个孩子,如图假设是左孩子(要删除节点):
我们要把7的左孩子托管给被删除节点的父亲,也就是10->_left,因为root是10->_left 的别名,于是有root = root->_left(10->_left = root->_left),把没有孩子的节点归于只有左孩子;
代码实现:
bool EraseR(const K& key)
{
return _EraseR(_root,key);
}
bool _EraseR(Node*& root,const K& key)
{
if(root == nullptr)
{
return false;
}
if(root->_key < key)
{
_EraseR(root->_right,key);
}
else if(root->_key > key)
{
_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* Minright = root->_right;
while(Minright->_left)
{
Minright = Minright->_left;
}
swap(root->_key,Minright->_key);
return _EraseR(root->_right,key);
}
delete del;
return true;
}
}
2、 二叉搜索树递归完整代码
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
Node* CopyTree(Node* root)
{
if(root == nullptr)
{
return nullptr;
}
Node* CopyNode = new Node(root->_key);
CopyNode->_left = CopyTree(root->_left);
CopyNode->_right = CopyTree(root->_right);
return CopyNode;
}
void DestroyTree(Node* root)
{
if(root == nullptr)
{
return;
}
DestroyTree(root->_left);
DestroyTree(root->_right);
delete root;
}
public:
BSTree() = default;//c++11中强制编译器自己生成默认构造函数
BSTree(const BSTree<K>& t)
{
_root = CopyTree(t._root);
}
~BSTree()
{
DestroyTree(_root);
_root = nullptr;
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root,t._root);
return *this;
}
bool InsertR(const K& key)
{
return _InsertR(_root,key);
}
bool FindR(const K& key)
{
return _FindR(_root,key);
}
bool EraseR(const K& key)
{
return _EraseR(_root,key);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
bool _FindR(const Node* root,const K& key)
{
if(root == nullptr)
{
return false;
}
if(root->_key < key)
{
_FindR(root->_right,key);
}
else if(root->_key > key)
{
_FindR(root->_left,key);
}
else
{
return true;
}
}
bool _InsertR(Node*& root,const K& key)
{
if(root == nullptr)
{
//这里需要做解释
root = new Node(key);
return true;
}
if(root->_key < key)
{
_InsertR(root->_right,key);
}
else if(root->_key > key)
{
_InsertR(root->_left,key);
}
else
{
return false;
}
}
bool _EraseR(Node*& root,const K& key)
{
if(root == nullptr)
{
return false;
}
if(root->_key < key)
{
_EraseR(root->_right,key);
}
else if(root->_key > key)
{
_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* Minright = root->_right;
while(Minright->_left)
{
Minright = Minright->_left;
}
swap(root->_key,Minright->_key);
return _EraseR(root->_right,key);
}
delete del;
return true;
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
三、二叉搜索树的应用
1、K模型:
K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。(我们上面写的就是K模型)
比如:
🌞给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
🌞在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2、 KV模型:
每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
比如:
🌞英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
🌞再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
KV模型的节点描述:
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)
{}
};
可以自行实现KV模型的二叉搜索树,方法和K模型一样。