前言:我们知道二叉树概念以及其相关的数据操作,而二叉搜索树作为树形结构之一,二叉搜索树的树的性质决定了其在对数据的搜索和查找上效率会提高很多。
编译环境:VS2013
一、概念
二叉搜索树又称二叉排序数,树可为空,也可不为空。
当树不为空时,树满足:
1.非空左子树上所有节点的值都小于根节点的值
2.非空右子树的所有节点的值都大于根节点的值
3.树的左右子树也都为二叉搜索树
则此树为一二叉搜索树。
二、操作
1 数据查找
由二叉搜索树的性质可得,对二叉搜索树进行中序遍历为有序序列,最左侧的节点值最小,最右侧的节点值最大。
从根节点开始比较,进行查找,比根节点值小则往左边走,比根节点值大则往右边走,最多查找到高度次,走到空,则表明数据不存在。
2 数据插入
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
当然,前提为二叉搜索树中元素不重复,在进行节点插入之前,我们得检测一下插入数据是否存在,插入数据已在此树中存在就不可进行插入。
3 数据删除
a.若为空树,无删除元素,直接返回即可。
b.若树非空,我们可以分成以下几种情况进行讨论。
1)当删除节点无左右孩子时,节点可直接删除。
2)当删除节点只有左孩子时。
3)当删除节点只有右孩子时。
4)当删除节点左右孩子都存在时。
当这种情况出现时,因为删除节点后树必须仍为二叉树,此种情况我们采用替代节点删除。
在待删除节点的左右子树中去找替代节点,根据二叉搜索树的性质找。左子树中找最大的,即最右侧的节点;右子树中找最小的,即最左侧的的节点。
总结:对于二叉搜索树节点的删除,当删除节点只有左孩子和节点为叶子节点情况归为一类,其他两类为只有右孩子和左右孩子都存在。
三、应用
1 K模型
K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2 KV模型
每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
此内容涉及到之后的树型结构的关联式容器,主要有:map、multimap、set、multiset。
四、实现
1 数据操作
//节点创建
template<class T>
struct BSTNode
{
BSTNode(const T& val = T())
:_left(nullptr)
, _right(nullptr)
, _val(val)
{}
BSTNode<T>* _left;
BSTNode<T>* _right;
T _val;
};
//数据查找
Node* Find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_val==val)
return cur;
else if (val < cur->_val)
cur = cur->_left;
else
cur = cur->_right;
}
return nullptr;
}
//数据插入
bool Insert(const T& val)
{
//1.当树为空树,节点直接进行插入,插入节点为根节点
if (nullptr == _root)
{
_root = new Node(val);
return true;
}
//2.当树非空
//a.查找插入数据在树中是否存在(借助cur在BSTree中查找值为val的节点是否存在)
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_val == val)
return false;
else if (cur->_val > val)
cur = cur->_left;
else
cur = cur->_right;
}
//b.插入新节点
cur = new Node(val);
if (parent->_val > val)
parent->_left = cur;
else
parent->_right = cur;
return true;
}
//数据删除
bool Erase(const T& val)
{
//1.当树为空树直接返回
if (nullptr == _root)
return false;
//2.当树非空
//a.找到值为val的节点
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_val == val)
break;
else if (cur->_val > val)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//循环跳出情况,一是cur为空,二是元素找到后跳出循环
//确认元素是否找到
if (cur == nullptr)
return false;
//b.删除找到的节点
//对情况进行分类:
//情况一:欲删除节点只有左孩子或为叶子节点时
//情况二:欲删除节点只有右孩子时
//情况三:欲删除节点左右孩子均存在
Node* del = cur;
//情况一:欲删除节点只有左孩子或为叶子节点时
if (cur->_right == nullptr)
{
if (parent == nullptr)
{
//cur为根节点
_root = cur->_left;
}
else
{
//cur不为根节点,即parent不为空
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
}
//情况二:欲删除节点只有右孩子时
else if (cur->_left == nullptr)
{
if (parent==nullptr)
{
//cur为根节点
_root = cur->_right;
}
else
{
//cur不为根节点,即parent不为空
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
}
//情况三:欲删除节点左右孩子均存在
else
{
//cur不可直接删除,需在子树中找替代节点进行删除,将情况进行转化为一或二
//在右子树中找替代节点,找最小的节点
del = cur->_right;
parent = cur;
//在左子树中找最大的节点
while (del->_left)
{
parent = del;
del = del->_left;
}
//将del中值域替换cur中值域
cur->_val = del->_val;
//将del节点删除
if (del == parent->_left)
parent->_left = del->_right;
else
parent->_right = del->_right;
}
delete del;
return true;
}
2 代码测试
void Test()
{
BSTree<int> t;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
//插入节点
t.Insert(e);
}
t.Inorder();
//插入值为9的节点
if (!t.Find(9))
{
t.Insert(9);
}
t.Inorder();
//删除值为8的节点
t.Erase(8);
t.Inorder();
}
3 完整代码展示
//BinarySearchTree.hpp
#pragma once
template<class T>
struct BSTNode
{
BSTNode(const T& val = T())
:_left(nullptr)
, _right(nullptr)
, _val(val)
{}
BSTNode<T>* _left;
BSTNode<T>* _right;
T _val;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
public:
BSTree()
:_root(nullptr)
{}
~BSTree()
{
DestroyBSTree(_root);
}
//数据查找
Node* Find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_val==val)
return cur;
else if (val < cur->_val)
cur = cur->_left;
else
cur = cur->_right;
}
return nullptr;
}
//数据插入
bool Insert(const T& val)
{
//1.当树为空树,节点直接进行插入,插入节点为根节点
if (nullptr == _root)
{
_root = new Node(val);
return true;
}
//2.当树非空
//a.查找插入数据在树中是否存在(借助cur在BSTree中查找值为val的节点是否存在)
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_val == val)
return false;
else if (cur->_val > val)
cur = cur->_left;
else
cur = cur->_right;
}
//b.插入新节点
cur = new Node(val);
if (parent->_val > val)
parent->_left = cur;
else
parent->_right = cur;
return true;
}
//数据删除
bool Erase(const T& val)
{
//1.当树为空树直接返回
if (nullptr == _root)
return false;
//2.当树非空
//a.找到值为val的节点
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_val == val)
break;
else if (cur->_val > val)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//循环跳出情况,一是cur为空,二是元素找到后跳出循环
//确认元素是否找到
if (cur == nullptr)
return false;
//b.删除找到的节点
//对情况进行分类:
//情况一:欲删除节点只有左孩子或为叶子节点时
//情况二:欲删除节点只有右孩子时
//情况三:欲删除节点左右孩子均存在
Node* del = cur;
//情况一:欲删除节点只有左孩子或为叶子节点时
if (cur->_right == nullptr)
{
if (parent == nullptr)
{
//cur为根节点
_root = cur->_left;
}
else
{
//cur不为根节点,即parent不为空
if (cur == parent->_left)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
}
//情况二:欲删除节点只有右孩子时
else if (cur->_left == nullptr)
{
if (parent==nullptr)
{
//cur为根节点
_root = cur->_right;
}
else
{
//cur不为根节点,即parent不为空
if (cur == parent->_left)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
}
//情况三:欲删除节点左右孩子均存在
else
{
//cur不可直接删除,需在子树中找替代节点进行删除,将情况进行转化为一或二
//在右子树中找替代节点,找最小的节点
del = cur->_right;
parent = cur;
//在左子树中找最大的节点
while (del->_left)
{
parent = del;
del = del->_left;
}
//将del中值域替换cur中值域
cur->_val = del->_val;
//将del节点删除
if (del == parent->_left)
parent->_left = del->_right;
else
parent->_right = del->_right;
}
delete del;
return true;
}
void Inorder()
{
cout << "中序遍历:";
_Inorder(_root);
cout << endl;
}
private:
void _Inorder(Node* root)
{
if (root)
{
_Inorder(root->_left);
cout << root->_val << " ";
_Inorder(root->_right);
}
}
void DestroyBSTree(Node*& root)
{
if (root)
{
DestroyBSTree(root->_left);
DestroyBSTree(root->_right);
delete root;
root = nullptr;
}
}
private:
BSTNode<T>* _root;
};
void Test()
{
BSTree<int> t;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
//插入节点
t.Insert(e);
}
t.Inorder();
//插入值为9的节点
if (!t.Find(9))
{
t.Insert(9);
}
t.Inorder();
//删除值为8的节点
t.Erase(8);
t.Inorder();
}
//BSTree.cpp
#include<iostream>
using namespace std;
#include"BinarySearchTree.hpp"
int main()
{
Test();
system("pause");
return 0;
}