1.搜索二叉树的概念
搜索二叉树也称二叉排序树和二叉查找树,它具有以下性质:
2.搜索二叉树的实现
搜索二叉树无非也是一棵二叉树,所以它只有一些特殊的功能和普通二叉树不一样,我们实现主要它的增、删、查,因为搜索二叉树左节点要比这个节点小,右节点的值要比它大,所以是不能插入已经存在的值的结点,插入的值也是不能修改的。下面我们开始实现。
2.1插入
要插入结点,我们首先需要构建一个结点Node的结构体,里面存放左节点,右节点和要插入的key值,
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{
}
typedef BSTreeNode<K> node;
node* _left;
node* _right;
K _key;
};
template<class K>
class BinarySearchTree
{
typedef BSTreeNode<K> node;
public:
private:
node* _root = nullptr;
};
开始插入结点:
要插入这个值,我们首先要去找这个值应该插入的位置在哪里,如果这个值比根节点的值大,就应该去根节点的右子树找,如果小就去左子树找,如果要插入的值,与当前根节点值相等,那就证明已经存在当前的值,那不能插入,需要返回false。
bool Insert(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 false;//要插入值已经存在,返回false。
}
}
//....cur为空,要插入的值不存在,可以插入
}
代码运行到省略号这里,代表可以插入值,但是我们需要首先判断根节点_root是不是空结点,如果是空结点,我们访问空结点的左右子树是野指针的
bool Insert(const K& key)
{
if (_root == nullptr)//判断根为空的情况
{
_root = new node(key);
return true;
}
node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;//去右子树找
}
else if (cur->_key > key)
{
cur = cur->_left;//去左子树找,
}
else
{
return false;//要插入值已经存在,返回false。
}
}
//....cur为空,要插入的值不存在,可以插入
}
这个时候,我们就可以直接在省略号的位置,直接new一个值为key的结点。
cur = new node(key);
return true;
大家想一下,这样就结束了吗?很显然没有,因为这个cur是一个局部变量,我们只是new出了一个结点赋给了cur,但是并没有把它链接在这棵树上,所以,我们在找要插入的位置的时候,需要一个父节点,每次cur变成新的结点的之前,cur都需要先赋给parent。再者,我们最后查好的时候,需要判断cur到底插入在parent结点的左边还是右边即:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new node(key);
return true;
}
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
{
return false;
}
}
cur = new node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
这才是一个完整的插入,我们写一个中序遍历,插入几个结点,验证一下结果。
2.2中序遍历
void _Inorder(node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
因为中序需要递归,而函数的参数默认有this->_root,所以我们不需要传参,但是递归需要传参,所以在这里我们封装一个_Inorder(Node* root),真正的中序函数,直接调用就可以啦。
测试:
#include"BSTree.h"
void test1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BinarySearchTree<int> b;
for (auto e : a)
{
b.Insert(e);
}
b.Inorder();
}
int main()
{
test1();
return 0;
}
结果与预期相符
2.3查找
查找其实我们插入一开始就写的过,就是看key比根节点大还是小,大就去根的右边,小就去左边,相等就是找到了。
bool 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 true;
}
}
return false;
}
2.4删除
删除算是搜索二叉树里面难度相对比较高的,很多细节我们需要考虑。
首先,我们考虑,删除的结点如果没有孩子,也就是删除叶子结点,还是比较容易的,直接把这个结点删除就可以了;如果删除的结点有一个孩子,也就是左右子树有一个为空,其实相对也还能实现,我们只需让自己的父节点指向自己自己的孩子就可以了。就比如如果要删除当前节点,但是当前节点的左子树为空,那就可以直接让父节点指向自己的右子树,然后删除当前节点就可以了;但是还有一种情况,如要删除的结点左右子树都不为空怎么办?父节点最多只能指向一个,现在我有两个结点,该怎么删?这里介绍一种方法——替换法。即要删除的结点左右孩子都不为空,我们不能破坏搜索树的规则,那就去找到左子树的最大值,或者我们找到右子树的最小值,找到其中一个,然后与要删除的值替换,然后再删除我们要删除的值。因为我们去找左子树的最大值,首先它要比要删除结点cur的其它左子树都要大,比cur右子树的所有结点都要小,况且它身为cur左子树的最大值,它断然是没有右子树的,那把这个结点的值赋给cur,我们都把左最大的值给要删除的cur了,然后删除左最大这个结点不就Ok了嘛;同理我们也可以找cur右子树最小值,它会比所有cue的右子树结点都小,比所有cur左子树结点都大,它身为cur右树最小值结点,肯定是没有左子树的。那这个左树最大结点,右树最小结点怎么找呢?右树最小结点,一定在右子树最左边,因为它的最小的,它一定是别人的左子树,所以它一直都在左边,直到它这里没有左子树了,它就是最小的。同理,左子树最大值也是左子树最右边的结点。
下面我们慢慢来实现,这里是以找右树最小结点为例
bool Erase(const K& key)
{
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)//cur左边为空,把右孩子交给父节点
{
if (cur == _root)//左子树为空,且要删根节点情况,直接让右孩子为根
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)//
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)//cur右树为空
{
if (cur == _root)//判断右树为空,删除_root情况
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
return true;
}
else//替换法:处理要删除的结点左右孩子都不为空
{
node* RightParent = cur;
node* Rightmin = cur->_right;
while (Rightmin->_left)//去右树左边找右树最小结点
{
RightParent = Rightmin;
Rightmin = Rightmin->_left;
}
cur->_key = Rightmin->_key;//右树最小结点的值赋给要删除的cur,删除右树最小结点
if (RightParent->_left == Rightmin)
RightParent->_left = Rightmin->_right;
else
RightParent->_right = Rightmin->_right;
delete Rightmin;
return true;
}
}
}
return false;
}
我们测试结果
void test1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BinarySearchTree<int> b;
for (auto e : a)
{
b.Insert(e);
}
b.Inorder();
b.Erase(8);//删除根节点8
b.Inorder();
}
int main()
{
test1();
return 0;
}
再把所有结点都删一遍
void test1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BinarySearchTree<int> b;
for (auto e : a)
{
b.Insert(e);
}
b.Inorder();
b.Erase(8);//删除根节点8
b.Inorder();
cout << "********************" << endl;
for (auto e : a)
{
b.Erase(e);
b.Inorder();
}
}
验证可以看到,我们的删除并没有问题。
这次分享就到这里,下面附上源码
3.源码
//面向对象面向君,不负代码不负卿
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{
}
typedef BSTreeNode<K> node;
node* _left;
node* _right;
K _key;
};
template<class K>
class BinarySearchTree
{
typedef BSTreeNode<K> node;
public:
void _Inorder(node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
bool 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 true;
}
}
return false;
}
bool Insert(const K& key)
{
if (_root == nullptr)//判断根为空的情况
{
_root = new node(key);
return true;
}
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
{
return false;//要插入值已经存在,返回false。
}
}
//....cur为空,要插入的值不存在,可以插入
cur = new node(key);
if (parent->_key > key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Erase(const K& key)
{
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)//cur左边为空,把右孩子交给父节点
{
if (cur == _root)//左子树为空,且要删根节点情况,直接让右孩子为根
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)//
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)//cur右树为空
{
if (cur == _root)//判断右树为空,删除_root情况
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
return true;
}
else//替换法:处理要删除的结点左右孩子都不为空
{
node* RightParent = cur;
node* Rightmin = cur->_right;
while (Rightmin->_left)//去右树左边找右树最小结点
{
RightParent = Rightmin;
Rightmin = Rightmin->_left;
}
cur->_key = Rightmin->_key;//右树最小结点的值赋给要删除的cur,删除右树最小结点
if (RightParent->_left == Rightmin)
RightParent->_left = Rightmin->_right;
else
RightParent->_right = Rightmin->_right;
delete Rightmin;
return true;
}
}
}
return false;
}
private:
node* _root = nullptr;
};