1. 概念
二叉搜索树又称二叉排序树,它可以是一棵空树,也可以是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于或大于根节点的值。
- 若它的右子树不为空,则右子树上所有节点的值都大于或小于根节点的值。
- 它的左右子树也分别为二叉搜索树。
比如
如果按照中序遍历这颗二叉树,得到的序列为升序序列。
2. 二叉搜索树的实现
这里按照左子树的值小于根节点的值,右子树的值大于根节点的值的方式实现。
2.1 插入
先定义这样的一个模板,成员分别为存储的数据、左孩子指针和右孩子指针
template<class T>
struct BSTreeNode
{
BSTreeNode(const T& val)
:_val(val),
_left(nullptr),
_right(nullptr)
{}
T _val;
BSTreeNode<T>* _left;
BSTreeNode<T>* _right;
};
插入大致过程如下,这里默认不能插入相同数据。
bool insert(const T& val)
{
if (_root == nullptr) //空树
{
_root = new Node(val);
return true;
}
Node* cur = _root;
Node* parent = _root; //保留父亲结点
while (cur)
{
if (val > cur->_val) //val值大,往右
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val) //val值小,往左
{
parent = cur;
cur = cur->_left;
}
else //相等 返回
return false;
}
cur = new Node(val);
if (val > parent->_val) //判断插入在父节点的哪一边
parent->_right = cur;
else
parent->_left = cur;
return true;
}
2.2 删除
首先找到待删除的结点,方式和也是比较存储的数据的大小。
找到后,需要分以下两种情况进行删除
总结:
对于情况1:先找待删除结点,将这个结点的孩子交给这个结点的父亲管理。
对于情况2:先找待删除结点,再找左树的最右结点或右数的最左结点。交换数据,再删除,此时会转化为情况1。
代码如下
bool erase(const T& val)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else
{
//0个或1个孩子
if (cur->_left == nullptr)
{
if (parent == nullptr) //删除结点为根结点
_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)
{
if (parent == nullptr) //删除结点为根结点
_root = cur->_left;
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
else //两个孩子
{
Node* rightMinPar = cur; //最左结点的父亲
Node* rightMin = cur->_right; //最左结点
//找右子树的最左结点
while (rightMin->_left)
{
rightMinPar = rightMin;
rightMin = rightMin->_left;
}
// 赋值
cur->_val = rightMin->_val;
if (rightMinPar->_right==rightMin)
rightMinPar->_right = rightMin->_right;
else
rightMinPar->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
2.3 查找
由于二叉搜索树的性质,不允许修改存储的数据,修改会破坏搜索树的性质
//查找
Node* find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (val > cur->_val)
cur = cur->_right;
else if (val < cur->_val)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
2.4 中序遍历
再类外无法访问私有成员,因此,再类内会套用一层函数。
void inorder() //公有
{
Inorder(_root);
cout << endl;
}
void Inorder(Node* root) //私有
{
if (root == nullptr)
return;
Inorder(root->_left);
cout << root->_val << " ";
Inorder(root->_right);
}
测试程序
int main()
{
BSTree<int> bs;
int a[] = { 8,3,10,1,6,14,4,7,13 };
for (auto& val : a)
{
bs.insert(val);
}
bs.inorder();
for (auto& val : a)
{
bs.erase(val);
bs.inorder();
}
return 0;
}
运行结果
3. 搜索树的应用
-
K模型:K模型即只有key作为关键码,结构中只需要存储key即可,关键码就是要搜索到的值。
-
KV模型:每一个关键码key,都有与之对应的值value,即<key, value>的键值对。通过key查找value。比如单词查询。
-
AVL树和红黑树。
搜索树在大多数情况性能都比较好,但是最差情况下,会退化为单支树。此时效率就不高。AVL树和红黑树可以解决这种情况。
4. 完整代码
BSTree.h
#pragma once
#include<iostream>
using namespace std;
template<class T>
struct BSTreeNode
{
BSTreeNode(const T& val)
:_val(val),
_left(nullptr),
_right(nullptr)
{}
T _val;
BSTreeNode<T>* _left;
BSTreeNode<T>* _right;
};
template<class T>
class BSTree
{
typedef BSTreeNode<T> Node;
public:
//插入
bool insert(const T& val)
{
if (_root == nullptr) //空树
{
_root = new Node(val);
return true;
}
Node* cur = _root;
Node* parent = _root; //保留父亲结点
while (cur)
{
if (val > cur->_val) //val值大,往右
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val) //val值小,往左
{
parent = cur;
cur = cur->_left;
}
else //相等 返回
return false;
}
cur = new Node(val);
if (val > parent->_val)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
//查找
Node* find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (val > cur->_val)
cur = cur->_right;
else if (val < cur->_val)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
//删除
bool erase(const T& val)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else
{
//0个或1个孩子
if (cur->_left == nullptr)
{
if (parent == nullptr) //删除结点为根结点
_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)
{
if (parent == nullptr) //删除结点为根结点
_root = cur->_left;
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
else //两个孩子
{
Node* rightMinPar = cur; //最左结点的父亲
Node* rightMin = cur->_right; //最左结点
//找右子树的最左结点
while (rightMin->_left)
{
rightMinPar = rightMin;
rightMin = rightMin->_left;
}
// 赋值
cur->_val = rightMin->_val;
if (rightMinPar->_right==rightMin)
rightMinPar->_right = rightMin->_right;
else
rightMinPar->_left = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
void inorder()
{
Inorder(_root);
cout << endl;
}
private:
void Inorder(Node* root)
{
if (root == nullptr)
return;
Inorder(root->_left);
cout << root->_val << " ";
Inorder(root->_right);
}
private:
Node* _root = nullptr;
};