一、二叉树搜索树
1.1 二叉搜索树的概念
**二叉搜索树(BST、Binary Search Tree)**又称二叉排序树或二叉查找树,可以是一棵空树,也可以是具有以下性质的特殊二叉树:
cpp
1.如果左子树不为空,那么左子树上所有的节点的值(key)都小于根节点的值(key)。
2.如果右子树不为空,那么右子树上所有的节点的值(key)都大于根节点的值(key)。
3.左右子树也是一棵搜索二叉树。
4.搜索二叉树不允许有重复的值(key)。

默认的二叉搜索树走中序遍历结果是升序。
1.2 二叉搜索树的操作
对于下面这棵二叉搜索树:

cpp
int a[] = { 8,3,1,10,6,4,7,14,13 };
1.2.1 二叉搜索树的查找
Delphi
1.如果要查找的值(key)比根节点的值(key)要大,那么往右子树继续查找,如果比根节点的值(key)
小,那么往左子树查找。
2.最多查找高度次,如果走到空还没有找到,说明这个值不在该二叉搜索树里。
1.2.2 二叉搜索树的插入
cpp
两种情况
1.树为空,则直接新增一个节点,并将该节点赋值给根节点。
2.树不为空,先查找插入位置,然后插入新节点。
依次插入16、0。插入成功返回true,插入失败返回false。

1.2.3 二叉搜索树的删除
cpp
首先查找要删除的值是否在树中,如果不在,则返回false。如果在树中,又可能分为以下四种情况:
1.被删除的节点没有左右孩子。
1.被删除的节点只有左孩子。
2.被删除的节点只有右孩子。
3.被删除的节点既有左孩子,又有右孩子。
情况1可以和情况2或3合并。合并以后的新情况:
cpp
1.被删除的节点只有左孩子,左孩子可能为空树。
2.被删除的节点只有右孩子。
3.被删除的节点既有左孩子,又有右孩子。
cpp
情况1.被删除的节点只有左孩子,左孩子可能为空树。
情况1的删除方法:

情况1存在的特殊情况以及处理方法:根节点只有左子树

cpp
情况2.被删除的节点只有右孩子。
情况2的处理方式以及情况2的特殊情况的处理方式:

cpp
情况3:被删除的节点既有左孩子又有有孩子。
情况3的处理方法:
cpp
替换法:
1.找到左子树的最大节点(最右节点),或右子树的最小节点(最左节点)。
2.左子树的最右节点必定是没有右子树的,右子树的最左节点必定是没有左子树的。
3.将被删除的节点替换最右节点或最左节点。
4.删除替换后的最右节点或最左节点。

以节点8和找右子树的最左节点为例:

二、二叉搜索树的实现
2.1 二叉搜索树的框架结构
1.二叉树的每个节点的结构
cpp
包含:模板参数K、一个显示构造函数、节点的值_key、指向左孩子的节点指针_left、指向右孩子的
节点指针_right。
cpp
//1.二叉树的每个节点的结构
template<class K>
struct BSTreeNode
{
//构造函数
BSTreeNode(const K& key=K())
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
//成员变量
K _key; //key值
BSTreeNode<K>* _left; //指向左孩子
BSTreeNode<K>* _right; //指向右孩子
};
2.二叉树的结构
cpp
包含:模板参数K、对节点名称的重命名,根节点_root,默认成员函数、功能成员函数
cpp
//模板参数K
template<class K>
class BSTree
{
//对节点重命名
typedef BSTreeNode<K> Node;
public:
//成员函数
//1.默认成员函数
//构造函数
BSTree();
//拷贝构造函数
BSTree(const BSTree<K>& tree);
//赋值重载函数
BSTree<K>& operator=(BSTree<K> tree);
//析构函数
~BSTree();
//...
//2.功能成员函数
//查找
bool Find(const K& key);
//插入
bool Insert(const K& key);
//删除
bool Erase(const K& key);
//中序遍历
void InOrder();
//...
private:
//成员变量
Node* root = nullptr;
};
其中功能成员函数又有递归版本和非递归版本。
2.2 实现
2.2.1 Find查找
非递归版本:
cpp
//查找
bool Find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key > cur->_key)
{
//继续去右子树查找
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树查找
cur = cur->_left;
}
else
{
//找到了
return true;
}
}
//没找到
return false;
}
递归版本:
cpp
//递归版本的查找
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key == key)
{
return true;
}
else if (root->_key < key)
{
return _FindR(root->_right, key);
}
else
{
return _FindR(root->_left, key);
}
}
2.2.2 插入Insert
非递归版本:
cpp
//插入
bool Insert(const K& key)
{
//如果根为空,直接插入新节点
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
//记录cur的父节点
Node* prev = _root;
while (cur != nullptr)
{
prev = cur;
if (key > cur->_key)
{
//继续去右子树
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树
cur = cur->_left;
}
else
{
//搜索二叉树不允许有重复值
return false;
}
}
//插入
if (key > prev->_key)
{
prev->_right = new Node(key);
}
else
{
prev->_left = new Node(key);
}
return true;
}
递归版本:
cpp
递归版本的插入使用了引用传参,可以在找到插入位置时修改root可以直接插入
cpp
//递归版本
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)
{
//搜索二叉树不允许有重复值
return false;
}
else if (root->_key > key)
{
_InsertR(root->_left, key);
}
else
{
_InsertR(root->_right, key);
}
return true;
}
2.2.3 删除
非递归版本:
cpp
//删除
bool Erase(const K& key)
{
Node* cur = _root;
//指向cur的父节点
Node* prev = _root;
while (cur&&cur->_key!=key)
{
prev = cur;
if (cur->_key > key)
{
//去左子树
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右子树
cur = cur->_right;
}
}
if (cur == nullptr)
{
//没找到要删除的节点
return false;
}
//找到了要删除的节点
//开始删除
if (cur->_left && cur->_right)
{
//左右孩子均存在
//1.找右子树的最左节点
Node* tmp = cur->_right;
Node* parent = cur;
while (tmp->_left != nullptr)
{
parent = tmp;
tmp = tmp->_left;
}
//交换最左节点和cur节点的值_key
swap(cur->_key, tmp->_key);
//如果parent没有动,说明cur的第一个右孩子就是最左节点
if (parent == cur)
{
cur->_right = tmp->_right;
}
else
{
//如果parent动了,那么tmp就必定是parent的左孩子
parent->_left = tmp->_right;
}
delete tmp;
}
else if (cur->_right)
{
//只有右孩子
//1.特殊情况:如果cur等于_root
if (cur == _root)
{
_root = cur->_right;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_right;
}
else
{
//是右孩子
prev->_right = cur->_right;
}
}
delete cur;
}
else
{
//只有左孩子或没有孩子
if (cur == _root)
{
_root = cur->_left;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_left;
}
else
{
//是右孩子
prev->_right = cur->_left;
}
}
delete cur;
}
return true;
}
递归版本:
cpp
递归版本的删除也使用的引用传参,可以更方便直接删除节点。
cpp
//删除的递归版本
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)
{
return _EraseR(root->_left, key);
}
else if (root->_key < key)
{
return _EraseR(root->_right, key);
}
else
{
Node* cur = root;
//找到了要删除的节点
if (root->_left && root->_right)
{
//有左右孩子
//1.找右子树的最左节点
Node* tmp = root->_right;
while (tmp->_left != nullptr)
{
tmp = tmp->_left;
}
//交换最左节点和cur节点的值_key
swap(root->_key, tmp->_key);
//cur的右子树仍然能确保是一棵二叉搜索树,在cur的右子树里递归删除key
_EraseR(root->_right, key);
}
else if (root->_right)
{
//只有右孩子
root = root->_right;
delete cur;
}
else
{
//只有左孩子或没有孩子
root = root->_left;
delete cur;
}
return true;
}
}
2.2.4 中序遍历
递归版本:
cpp
void InOrder()
{
_InOrderR(_root);
}
void _InOrderR(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrderR(root->_left);
cout << root->_key << " ";
_InOrderR(root->_right);
}
2.2.5 构造函数
cpp
//构造函数
BSTree()
:_root(nullptr)
{}
2.2.6 拷贝构造函数
二叉搜索树的拷贝构造需要深拷贝。
cpp
//拷贝构造函数
BSTree(const BSTree<K>& tree)
{
//需要进行深拷贝
copy(_root, tree._root);
}
//root2拷贝至root1
void copy(Node*& root1, Node* root2)
{
if (root2 == nullptr)
{
root1 = nullptr;
return;
}
root1 = new Node(root2->_key);
copy(root1->_left, root2->_left);
copy(root1->_right, root2->_right);
}
2.2.7 赋值重载函数
cpp
//赋值重载函数
BSTree<K>& operator=(BSTree<K> tree)
{
swap(_root, tree._root);
return *this;
}
2.2.8 析构函数
cpp
//析构函数
~BSTree()
{
destroy(_root);
}
void destroy(Node*& root)
{
if (root == nullptr)
{
return;
}
destroy(root->_left);
destroy(root->_right);
delete root;
root = nullptr;
}
2.2.9 整体代码
cpp
//1.二叉树的每个节点的结构
template<class K>
struct BSTreeNode
{
//构造函数
BSTreeNode(const K& key=K())
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
//成员变量
K _key; //key值
BSTreeNode<K>* _left; //指向左孩子
BSTreeNode<K>* _right; //指向右孩子
};
//模板参数K
template<class K>
class BSTree
{
//对节点重命名
typedef BSTreeNode<K> Node;
public:
//构造函数
BSTree()
:_root(nullptr)
{}
//拷贝构造函数
BSTree(const BSTree<K>& tree)
{
//需要进行深拷贝
copy(_root, tree._root);
}
//root2拷贝至root1
void copy(Node*& root1, Node* root2)
{
if (root2 == nullptr)
{
root1 = nullptr;
return;
}
root1 = new Node(root2->_key);
copy(root1->_left, root2->_left);
copy(root1->_right, root2->_right);
}
//赋值重载函数
BSTree<K>& operator=(BSTree<K> tree)
{
swap(_root, tree._root);
return *this;
}
//析构函数
~BSTree()
{
destroy(_root);
}
void destroy(Node*& root)
{
if (root == nullptr)
{
return;
}
destroy(root->_left);
destroy(root->_right);
delete root;
root = nullptr;
}
//2.功能成员函数
//查找
bool Find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key > cur->_key)
{
//继续去右子树查找
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树查找
cur = cur->_left;
}
else
{
//找到了
return true;
}
}
//没找到
return false;
}
//递归版本的查找
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key == key)
{
return true;
}
else if (root->_key < key)
{
return _FindR(root->_right, key);
}
else
{
return _FindR(root->_left, key);
}
}
//插入
bool Insert(const K& key)
{
//如果根为空,直接插入新节点
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
//记录cur的父节点
Node* prev = _root;
while (cur != nullptr)
{
prev = cur;
if (key > cur->_key)
{
//继续去右子树
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树
cur = cur->_left;
}
else
{
//搜索二叉树不允许有重复值
return false;
}
}
//插入
if (key > prev->_key)
{
prev->_right = new Node(key);
}
else
{
prev->_left = new Node(key);
}
return true;
}
//递归版本的插入使用了引用传参,可以在找到插入位置时修改root可以直接插入
//递归版本
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)
{
//搜索二叉树不允许有重复值
return false;
}
else if (root->_key > key)
{
_InsertR(root->_left, key);
}
else
{
_InsertR(root->_right, key);
}
return true;
}
//删除
bool Erase(const K& key)
{
Node* cur = _root;
//指向cur的父节点
Node* prev = _root;
while (cur&&cur->_key!=key)
{
prev = cur;
if (cur->_key > key)
{
//去左子树
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右子树
cur = cur->_right;
}
}
if (cur == nullptr)
{
//没找到要删除的节点
return false;
}
//找到了要删除的节点
//开始删除
if (cur->_left && cur->_right)
{
//左右孩子均存在
//1.找右子树的最左节点
Node* tmp = cur->_right;
Node* parent = cur;
while (tmp->_left != nullptr)
{
parent = tmp;
tmp = tmp->_left;
}
//交换最左节点和cur节点的值_key
swap(cur->_key, tmp->_key);
//如果parent没有动,说明cur的第一个右孩子就是最左节点
if (parent == cur)
{
cur->_right = tmp->_right;
}
else
{
//如果parent动了,那么tmp就必定是parent的左孩子
parent->_left = tmp->_right;
}
delete tmp;
}
else if (cur->_right)
{
//只有右孩子
//1.特殊情况:如果cur等于_root
if (cur == _root)
{
_root = cur->_right;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_right;
}
else
{
//是右孩子
prev->_right = cur->_right;
}
}
delete cur;
}
else
{
//只有左孩子或没有孩子
if (cur == _root)
{
_root = cur->_left;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_left;
}
else
{
//是右孩子
prev->_right = cur->_left;
}
}
delete cur;
}
return true;
}
//删除的递归版本
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)
{
return _EraseR(root->_left, key);
}
else if (root->_key < key)
{
return _EraseR(root->_right, key);
}
else
{
Node* cur = root;
//找到了要删除的节点
if (root->_left && root->_right)
{
//有左右孩子
//1.找右子树的最左节点
Node* tmp = root->_right;
while (tmp->_left != nullptr)
{
tmp = tmp->_left;
}
//交换最左节点和cur节点的值_key
swap(root->_key, tmp->_key);
//cur的右子树仍然能确保是一棵二叉搜索树,在cur的右子树里递归删除key
_EraseR(root->_right, key);
}
else if (root->_right)
{
//只有右孩子
root = root->_right;
delete cur;
}
else
{
//只有左孩子或没有孩子
root = root->_left;
delete cur;
}
return true;
}
}
//中序遍历
void InOrder()
{
_InOrderR(_root);
}
void _InOrderR(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrderR(root->_left);
cout << root->_key << " ";
_InOrderR(root->_right);
}
//...
private:
//成员变量
Node* _root = nullptr;
};
三、二叉搜索树的应用
3.1 K模型
K模型:K模型即只有key作为关键码,结构中只要存储key即可,关键码即为需要搜到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
cpp
1.以词库里所有单词作为key,构建一棵搜索二叉树。
2.在二叉树里检索该单词word是否存在,存在则拼写正确,如果不存在,则是该单词拼写错误。
3.2 K-V模型
KV模型:每一个关键码key,都有一个与之对应的value,即<key,value>的键值对。
比如英汉词典中的英文和汉语就是对应关系,通过英文可以快速找到对应的中文,<word,chinese>。
再比如统计单词,单词和单词出现的次数也是一对key-value模型,<word,count>。
3.3 改造K模型为K-V模型
cpp
删除不需要改变,每个节点的结构需要改变,插入也只需要新增一个参数value和new节点时的参
数,find返回值改为找到的节点的地址,遍历只需要多打印一个value。
cpp
//1.K-V模型的二叉树的每个节点的结构
template<class K,class V>
struct BSTreeNode
{
//构造函数
BSTreeNode(const K& key = K(),const V& value=V())
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
//成员变量
K _key; //key值
V _value;
BSTreeNode<K,V>* _left; //指向左孩子
BSTreeNode<K,V>* _right; //指向右孩子
};
//模板参数K,V
template<class K,class V>
class BSTree
{
//对节点重命名
typedef BSTreeNode<K,V> Node;
public:
//查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur != nullptr)
{
if (key > cur->_key)
{
//继续去右子树查找
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树查找
cur = cur->_left;
}
else
{
//找到了,返回当前节点
return cur;
}
}
//没找到
return nullptr;
}
//插入
bool Insert(const K& key,const V& value)
{
//如果根为空,直接插入新节点
if (_root == nullptr)
{
_root = new Node(key,value);
return true;
}
Node* cur = _root;
//记录cur的父节点
Node* prev = _root;
while (cur != nullptr)
{
prev = cur;
if (key > cur->_key)
{
//继续去右子树
cur = cur->_right;
}
else if (key < cur->_key)
{
//继续去左子树
cur = cur->_left;
}
else
{
//搜索二叉树不允许有重复值
return false;
}
}
//插入
if (key > prev->_key)
{
prev->_right = new Node(key, value);
}
else
{
prev->_left = new Node(key, value);
}
return true;
}
//删除
bool Erase(const K& key)
{
Node* cur = _root;
//指向cur的父节点
Node* prev = _root;
while (cur && cur->_key != key)
{
prev = cur;
if (cur->_key > key)
{
//去左子树
cur = cur->_left;
}
else if (cur->_key < key)
{
//去右子树
cur = cur->_right;
}
}
if (cur == nullptr)
{
//没找到要删除的节点
return false;
}
//找到了要删除的节点
//开始删除
if (cur->_left && cur->_right)
{
//左右孩子均存在
//1.找右子树的最左节点
Node* tmp = cur->_right;
Node* parent = cur;
while (tmp->_left != nullptr)
{
parent = tmp;
tmp = tmp->_left;
}
//交换最左节点和cur节点的值_key
swap(cur->_key, tmp->_key);
//如果parent没有动,说明cur的第一个右孩子就是最左节点
if (parent == cur)
{
cur->_right = tmp->_right;
}
else
{
//如果parent动了,那么tmp就必定是parent的左孩子
parent->_left = tmp->_right;
}
delete tmp;
}
else if (cur->_right)
{
//只有右孩子
//1.特殊情况:如果cur等于_root
if (cur == _root)
{
_root = cur->_right;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_right;
}
else
{
//是右孩子
prev->_right = cur->_right;
}
}
delete cur;
}
else
{
//只有左孩子或没有孩子
if (cur == _root)
{
_root = cur->_left;
}
else
{
//判断cur是父节点的左孩子还是右孩子
if (prev->_key > cur->_key)
{
//是左孩子
prev->_left = cur->_left;
}
else
{
//是右孩子
prev->_right = cur->_left;
}
}
delete cur;
}
return true;
}
//中序遍历
void InOrder()
{
_InOrderR(_root);
}
void _InOrderR(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrderR(root->_left);
cout << root->_key << root->_value << " ";
_InOrderR(root->_right);
}
//...
private:
//成员变量
Node* _root = nullptr;
};
四、二叉搜索树的性能分析
插入和删除都需要先进行查找,因此查找效率变成了代表二叉搜索树的各个操作的性能。
二叉搜索树查找的时间复杂度:O(N)
cpp
1.当二叉搜索树接近一棵完全二叉树或满二叉树时,查找效率为O(log(N))。
2.当二叉搜索树是以有序的顺序插入时,查找效率为O(N)。

在最坏情况下,二叉搜索树退化为了一只单支树,搜索性能就失去了,时间复杂度是按照最坏的情况进行计算的,因此二叉搜索树的时间复杂度为O(N)。