二叉搜索树
二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
之前我们找数暴力查找,例如在顺序表里面查找最多得查找表长个。但是如果放在搜索二叉树里面,搜索二叉树只找n*logn也就是树高度次数。
二叉树先构建节点
template
struct BinarySearchNode
{
BinarySearchNode(const K&key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
BinarySearchNode* _left;
BinarySearchNode* _right;
K _key;
};
之后节点构建之后再写树的结构
template
class BinarySearchTree
{
typedef BinarySearchNode Node;
private:
Node* _root=nullptr;
};
先写二叉树的插入:
bool insert(const K& key)
{
if(_root == nullptr)
{
_root = new Node(key);//new 要调用node的构造函数
return true;
}
Node* parent = nullptr;//只要是链式结构都得把它的前后链接起来
//插入得找位置
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;//cur的先走,随后parent=cur,之后等到cur到尾部就结束,恰巧parent在上面
//刚好链接
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 FindKey(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 true;
}
}
return false;
}
删除
有以下三种情况,但是第一二种情况可以归为一类;
下面开始写:
对称的如果cur的右边是空也是这种情况
所以代码的第一种情况应该这样写:
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.左为空
//2.右为空
//3.左右都不为空
if (cur->_left == nullptr)
{
if (cur == nullptr)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_left;
}
}
else if (cur->_right == nullptr)
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
}
return false;
}
但是如果我们干掉的是根节点的话,这时候根节点的parent是空,cur是根这种情况。
这时候如果不解决,下面的parent就变成了空指针解引用,会直接报错。
思路:我们只需要更新root就行,让删除根节点,让root等于cur的右边即可
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;
cur = nullptr;
}
else if (cur->_right == nullptr)//右为空
{
if (_root = cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
处理完上面这两种情况就剩余了左右子树都有的情况了:
处理办法如下:
else//左右都不是空
{
//替换法删除(可以用左子树的最大值替换,也可以用右子树的最小值计算)
//这里我们采取右子树的最小值
Node* Min = cur->_right;
Node* minParent = cur;//这里不能给空给空如果删除的是最上面的根就会出现下面
//minParent空指针的问题;
while (Min->_left)
{
minParent = Min;
Min = Min->_left;
}
swap(cur->_key, Min->_key);
//minParent->_left = Min->_right;
if (minParent->_left == Min)
{
minParent->_left = Min->_right;
}
else
{
minParent->_right = Min->_right;
}
delete Min;
}
搜索二叉树在当前这个模型不支持修改。
下面就递归的完整代码
template
struct BinarySearchNode
{
BinarySearchNode(const K&key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
BinarySearchNode* _left;
BinarySearchNode* _right;
K _key;
};
template
class BinarySearchTree
{
typedef BinarySearchNode Node;
public:
bool insert(const K& key)
{
if(_root == nullptr)
{
_root = new Node(key);//new 要调用node的构造函数
return true;
}
Node* parent = nullptr;//只要是链式结构都得把它的前后链接起来
//插入得找位置
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;//cur的先走,随后parent=cur,之后等到cur到尾部就结束,恰巧parent在上面
//刚好链接
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()//这样嵌套是因为递归必须得传一个参数,但是这里的参数是this调用的,出了类this不能出现
//想要空参调用,有三种处理方法,一种是把下面的test函数变成类的友元,另外一种是写一个接口getroot
//第三种就写这种加一个函数封装一下;
{
_Inorder(_root);
cout << endl;
}
bool FindKey(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 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//找到了(开始删除)
{
//1.左为空
//2.右为空
//3.左右都不为空
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;
cur = nullptr;
}
else if (cur->_right == nullptr)//右为空
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else//左右都不是空
{
//替换法删除(可以用左子树的最大值替换,也可以用右子树的最小值计算)
//这里我们采取右子树的最小值
Node* Min = cur->_right;
Node* minParent = cur;//这里不能给空给空如果删除的是最上面的根就会出现下面
//minParent空指针的问题;
while (Min->_left)
{
minParent = Min;
Min = Min->_left;
}
swap(cur->_key, Min->_key);
//minParent->_left = Min->_right;
if (minParent->_left == Min)
{
minParent->_left = Min->_right;
}
else
{
minParent->_right = Min->_right;
}
delete Min;
}
return true;
}
}
return false;
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
private:
Node* _root=nullptr;
};
直至删成空树我们发现我们的结构依然成立 。上面我们采用了非递归(循环)方法来写,现在我们采用递归的方法来写。
递归在前面二叉树便利有详细的讲解:
这里我们直接写他的find,跟之前一样,保证它的封装性在我们在封装一层:
find:
bool FindR(const K&key)
{
return _FindR(_root, key);
}
private:
bool _FindR(Node*root,const K&key)
{
//比这个值大就去它的右子树,反之去它的左子树;
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _FindR(_root->_right,key);
}
else if (root->_key > key)
{
return _FindR(_root->_left,key);
}
else
{
return true;
}
}
查找次数是树的高度
下面我们写插入:
bool _InsertR(Node*&root,const K& key)
{
//插入比它大就往右走,比它小就往左走,当root走到null的时候就是实际可以插入的时候
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else
{
//相等就不让插入
return false;
}
}
删除的话还是跟之前循环遍历的情况一样:
总共分为三种情况;
左右分别为0分析如下:
代码:
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
{
}
}
到了两个结点的情况:
else
{
//找右树的最左节点替换删除
Node* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(root->_key, min->_key);
//return EraseR(key);
return _EraseR(root->_right, key);
}
delete del;
return true;
解决拷贝的问题:
搜索二叉树查找的最坏的情况下是N(如果是有序的树--单支树)
理想状态下:如果这颗树是满二叉树,或者完全二叉树这种才能是logN
改进的树—-平衡树(1.AVL树,2.红黑树)
下面介绍两个模型:
Key的搜索模型,判断关键字在不在
场景1:检查单词拼写是否正确,把词库的单词全部插入到二叉树里面,去搜索比较,对就判断是正确的;
Key/Value模型--通过key去找value
场景1:简单的中英互译程序(通过一个值去找另外一个)
完整的key模型代码:
namespace Key
{
template
struct BinarySearchNode
{
BinarySearchNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
BinarySearchNode* _left;
BinarySearchNode* _right;
K _key;
};
template
class BinarySearchTree
{
typedef BinarySearchNode Node;
public:
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);//new 要调用node的构造函数
return true;
}
Node* parent = nullptr;//只要是链式结构都得把它的前后链接起来
//插入得找位置
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;//cur的先走,随后parent=cur,之后等到cur到尾部就结束,恰巧parent在上面
//刚好链接
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()//这样嵌套是因为递归必须得传一个参数,但是这里的参数是this调用的,出了类this不能出现
//想要空参调用,有三种处理方法,一种是把下面的test函数变成类的友元,另外一种是写一个接口getroot
//第三种就写这种加一个函数封装一下;
{
_Inorder(_root);
cout << endl;
}
bool FindKey(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 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//找到了(开始删除)
{
//1.左为空
//2.右为空
//3.左右都不为空
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;
cur = nullptr;
}
else if (cur->_right == nullptr)//右为空
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else//左右都不是空
{
//替换法删除(可以用左子树的最大值替换,也可以用右子树的最小值计算)
//这里我们采取右子树的最小值
Node* Min = cur->_right;
Node* minParent = cur;//这里不能给空给空如果删除的是最上面的根就会出现下面
//minParent空指针的问题;
while (Min->_left)
{
minParent = Min;
Min = Min->_left;
}
swap(cur->_key, Min->_key);
//minParent->_left = Min->_right;
if (minParent->_left == Min)
{
minParent->_left = Min->_right;
}
else
{
minParent->_right = Min->_right;
}
delete Min;
}
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);
}
~BinarySearchTree()
{
_Destory(_root);
}
BinarySearchTree()
{
}
//或者这样写:
//BinarySearchTree() = default;//强制编译器生成默认的构造
//拷贝构造怎么写?(前序遍历,遇见一个节点copy一个节点)(前序遍历:根,左,右)
BinarySearchTree(const BinarySearchTree& t)
{
_root = _Copy(t._root);
}
//拷贝构造也是构造,自己写了,编译器就不会有构造函数了,上面还得写一个空参的构造函数
//t2=t1;
BinarySearchTree& operator=(BinarySearchTreet)//t就是t1的拷贝
{
swap(_root, t._root);//t就是t2想要的也就是this->_root;
return *this;
}
private:
/*******************************************************************************************/
Node* _Copy(Node* root)//左右子树分别递归copy
{
if (root == nullptr)
{
return nullptr;
}
Node* copyRoot = new Node(root->_key);//copy结点跟你有一样的值即可
copyRoot->_left = _Copy(root->_left);
copyRoot->_right = _Copy(root->_right);
return copyRoot;
//遇见根拷贝根遇见左子树递归拷贝左子树
}
void _Destory(Node*& root)
{
if (_root == nullptr)
{
return;
}
_Destory(_root->_left);
_Destory(_root->_right);
delete root;
root = nullptr;
}
bool _FindR(Node* root, const K& key)
{
//比这个值大就去它的右子树,反之去它的左子树;
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _FindR(_root->_right, key);
}
else if (root->_key > key)
{
return _FindR(_root->_left, key);
}
else
{
return true;
}
}
bool _InsertR(Node*& root, const K& key)
{
//插入比它大就往右走,比它小就往左走,当root走到null的时候就是实际可以插入的时候
if (root == nullptr)
{
root = new Node(key);
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* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(root->_key, min->_key);
//return EraseR(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;
};
void TestBSTree1()
{
BinarySearchTree b1;
int a[] = { 8,3,1,10,6,4,7,14,13 };//这里如果传入多个相同值会被天然去重;
for (auto e : a)
{
b1.insert(e);
}
b1.Inorder();
for (auto e : a)
{
b1.Erase(e);
b1.Inorder();
}
}
void TestBSTree2()
{
BinarySearchTree b1;
int a[] = { 8,3,1,10,6,4,7,14,13 };//这里如果传入多个相同值会被天然去重;
for (auto e : a)
{
b1.InsertR(e);
}
b1.Inorder();
for (auto e : a)
{
b1.EraseR(e);
b1.Inorder();
}
}
void TestBSTree3()
{
BinarySearchTreeb1;
int a[] = { 8,3,1,10,6,4,7,14,13 };
for (auto e : a)
{
b1.InsertR(e);
}
BinarySearchTree copy = b1;//没写析构所以拷贝不会崩溃
copy.Inorder();
b1.Inorder();
}
}
完整的keyValue模型:
#include
namespace KeyValue
{
template
struct BinarySearchNode
{
BinarySearchNode(const K& key,const V&value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_value(value)
{}
BinarySearchNode* _left;
BinarySearchNode* _right;
K _key;
V _value;//找到K就找到Value了
};
template
class BinarySearchTree
{
typedef BinarySearchNode Node;
public:
bool insert(const K& key,const V&value)
{
if (_root == nullptr)
{
_root = new Node(key,value);//new 要调用node的构造函数
return true;
}
Node* parent = nullptr;//只要是链式结构都得把它的前后链接起来
//插入得找位置
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;//cur的先走,随后parent=cur,之后等到cur到尾部就结束,恰巧parent在上面
//刚好链接
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;
}
void Inorder()//这样嵌套是因为递归必须得传一个参数,但是这里的参数是this调用的,出了类this不能出现
//想要空参调用,有三种处理方法,一种是把下面的test函数变成类的友元,另外一种是写一个接口getroot
//第三种就写这种加一个函数封装一下;
{
_Inorder(_root);
cout << endl;
}
Node* FindKey(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.左为空
//2.右为空
//3.左右都不为空
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;
cur = nullptr;
}
else if (cur->_right == nullptr)//右为空
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else//左右都不是空
{
//替换法删除(可以用左子树的最大值替换,也可以用右子树的最小值计算)
//这里我们采取右子树的最小值
Node* Min = cur->_right;
Node* minParent = cur;//这里不能给空给空如果删除的是最上面的根就会出现下面
//minParent空指针的问题;
while (Min->_left)
{
minParent = Min;
Min = Min->_left;
}
swap(cur->_key, Min->_key);
//minParent->_left = Min->_right;
if (minParent->_left == Min)
{
minParent->_left = Min->_right;
}
else
{
minParent->_right = Min->_right;
}
delete Min;
}
return true;
}
}
return false;
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key << ": " << root->_value << endl;
_Inorder(root->_right);
}
private:
Node* _root = nullptr;
};
void TestBinarySearchTest01()//词库搜索单词
{
BinarySearchTree bs1;
bs1.insert("sort", "排序");
bs1.insert("right", "右边");
bs1.insert("left", "左边");
bs1.insert("string", "字符");
bs1.insert("insert", "插入");//按照字符串的Asscii码值比较
string str;
while (cin >> str)
{
BinarySearchNode* ret = bs1.FindKey(str);
if (ret)
{
cout << ":" << ret->_value << endl;
}
else
{
cout << "->无此单词" << endl;
}
}
}
void TestBinarySearchTest02()//计数器
{
string arr[] = {"苹果","苹果" ,"香蕉" ,"草莓" ,"香蕉" ,"苹果" ,"苹果" ,"苹果" };
BinarySearchTree countTree;
for (auto &str : arr)
{
BinarySearchNode* ret = countTree.FindKey(str);
if (ret)
{
ret->_value++;
}
else
{
countTree.insert(str, 1);
}
}
countTree.Inorder();
}
}