一.二叉搜索树的基本概念
二叉搜索树,也叫二叉排序树,是一种具有特殊性质的二叉树。
若左子树不空,则左子树上所有节点的值都小于根节点的值。
若右子树不空,则右子树上所有节点的值都大于根节点的值。
任意节点的子树也都是二叉搜索树。
如下面这颗二叉树:
我们可以看出这个二叉树符合上面的规则。
并且二叉搜索树还有一个重要的特点:
二叉搜索树的中序遍历,如果排序的是数字的话,那么他的中序遍历结果一定是个有序的数组,上面这个树的中序遍历为[1 15 16 17 18 20 23 24 27 28 30].
二.key模型和key-value模型
概念:
key的模型是指每个节点只有一个键值,用于确定节点在树中的位置。节点的键值必须满足二叉搜索树的性质,即左子节点的键值小于父节点的键值,右子节点的键值大于父节点的键值。这种模型比较简单,但是不能存储额外的信息。
key/value模型是指每个节点有一个键值和一个数据值,键值用于确定节点在树中的位置,数据值用于存储节点的附加信息。节点的键值仍然必须满足二叉搜索树的性质,但是数据值可以是任意类型或对象。这种模型比较灵活,可以实现一些高级功能,比如映射或字典。
一般而言,我们在程序中使用的都是key-value模型的二叉搜索树,因为key模型的没什么实际作用。
假设你想要存储图书的话,在key/value模型中就可以让图书姓名当作键值,数量当作数据值,以此来详细统计一下图书。
但是我们先由易到难,先来讲解一下key模型的二叉搜索树。
三.key模型的二叉搜索树操作
3.1 节点
无论是哪种模型,我们都需要定义一个内部类来表示节点,根据树的基本含义我们可以得知,除了key模型所需要的一个元素之外,我们还需要左右两个指针来表示每个节点的左孩子右孩子。
故得出:
template<typename K>
class BSTreeNode //二叉搜索树的节点类
{
public:
BSTreeNode(const K& key = K())
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
};
3.2 二叉树的表示
在整个二叉树的类中,我们只要保证有一个头节点就好了,因为他会指向自己的左右孩子。
故得出:
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node; //typedef一下节省代码量
private:
Node* _root = nullptr;
};
3.3 二叉搜索树的插入
3.3.1 思想
二叉搜索树的插入其实只需要注意一点,那就是,插入后,仍然保持二叉搜索树的性质。
那么我们就要与原有的树上的值进行比较,到了合适的位置后再进行插入。
比如说这张图:
如果我们要插入的值为 19 的话,需要进行如下几段比较
因此,平衡二叉树的插入实际上就是一个 比较大小 直到寻找到空位置 再进行插入的过程 。
但是,如果插入的元素在数中已经有了该怎么办呢?
平衡二叉树的key模型基本上是拒绝重复元素的插入的。(难办就别办了)
3.3.2 代码实现
bool Insert(const K& key)
{
//特殊情况,如果头节点为nullptr,直接插入
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//记录父节点,以方便链接
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
{
//待插入的数在树中已经存在,直接不办了
return false;
}
}
//进行链接
cur = new Node(key);
//再次进行比较 判断插入的节点是在左边还是右边
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
3.4 二叉搜索树的查找
3.4.1 简介
可以说,二叉搜索树在查找上面的进步是很大,他的平均查找速度为O(logN),最坏查找情况为O(N)。(N为树的高度)
为什么呢,其实类似于二分查找,它每次在查找的时候都只会前往左右区间,也就是直接剔除一半的值,最后便得出平均查找速度。
最坏查找是因为有特殊情况,(一般是有序)使二叉树退化成单子树,从而大幅降低了查找效率。
如:
3.4.2 思想
其实查找也是个比较大小的思想,通过一次一次的比较大小,最终得出结果。
如:
我们要查找 18
3.4.3 代码实现
Node* Find(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
{
//与插入不同,查找到返回节点
return cur;
}
}
//没有查找到 返回空
return nullptr;
}
3.5 二叉搜索树的删除
3.5.1 思想
二叉搜索树的删除和插入一样,都需要保持一个原则,插入后,仍然保持二叉搜索树的性质。
二叉搜索树的删除无非就四种情况:
1.删除没有孩子节点的父节点
2.删除没有左孩子,但有右孩子的父节点。
3.删除没有有孩子,但有左孩子的节父点。
4.删除有两个孩子的父节点
还是经典老图,稍微改一下,我们分别要删除16 15 28 24这四个节点,分别对应四种情况,我们来看一下。
其实第一种情况可以归类为2.3种情况
3.5.2 代码实现
bool Erase(const K& key)
{
//父指针记录 cur指针查找
Node* parent = nullptr;
Node* cur = _root;
//while循环遍历查找
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//查找到
//开始删除
//一共有三种情况
//待删除节点左指针为nullptr
if (cur->_left == nullptr) //第一二种 情况合起来
{
//处理特殊情况 如果要删除的是头指针
if (_root == cur)
{
_root = cur->_right;
}
else
{
//如果是节点左右都为nullptr 父节点的指针就指向nullptr
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
delete cur;
cur = nullptr;
}
//待删除节点右指针为nullptr
else if (cur->_right == nullptr)
{
//处理特殊情况 如果要删除的是头指针
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
//待删除节点左右孩子都有
else
{
//查找左子树的最大值 其实就是查找cur左子树的最右节点
/*Node* maxParent = cur;
Node* max = cur->_left;
while (max->_right)
{
maxParent = max;
max =max->_left;
}*/
Node* minParent = cur;
Node* min = cur->_right;
//查找右子树的最小值 其实就是查找cur右子树的最左节点
while (min->_left)
{
minParent = min;
min = min->_left;
}
//将找到的值和cur交换
swap(cur->_key, min->_key);
//如果查找到的最大值或者最小值 还有另外的节点 需要链接
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
min = nullptr;
}
return true;
}
}
//没有删除返回false
return false;
}
3.6 中序遍历
没啥可说的 普通二叉树的遍历罢了,除了中序遍历可能是一个有序数组外这一个性质外。
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << endl;
_InOrder(root->_right);
}
3.7 总代码
template<typename K>
class BSTreeNode //二叉搜索树的节点类
{
public:
BSTreeNode(const K& key = K())
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node; //typedef一下节省代码量
public:
bool Insert(const K& key)
{
//特殊情况,如果头节点为nullptr,直接插入
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//记录父节点,以方便链接
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
{
//待插入的数在树中已经存在,直接不办了
return false;
}
}
//进行链接
cur = new Node(key);
//再次进行比较 判断插入的节点是在左边还是右边
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
Node* Find(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
{
//与插入不同,查找到返回节点
return cur;
}
}
//没有查找到 返回空
return nullptr;
}
bool Erase(const K& key)
{
//父指针记录 cur指针查找
Node* parent = nullptr;
Node* cur = _root;
//while循环遍历查找
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//查找到
//开始删除
//一共有三种情况
//待删除节点左指针为nullptr
if (cur->_left == nullptr) //第一二种 情况合起来
{
//处理特殊情况 如果要删除的是头指针
if (_root == cur)
{
_root = cur->_right;
}
else
{
//如果是节点左右都为nullptr 父节点的指针就指向nullptr
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
delete cur;
cur = nullptr;
}
//待删除节点右指针为nullptr
else if (cur->_right == nullptr)
{
//处理特殊情况 如果要删除的是头指针
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
//待删除节点左右孩子都有
else
{
//查找左子树的最大值 其实就是查找cur左子树的最右节点
/*Node* maxParent = cur;
Node* max = cur->_left;
while (max->_right)
{
maxParent = max;
max =max->_left;
}*/
Node* minParent = cur;
Node* min = cur->_right;
//查找右子树的最小值 其实就是查找cur右子树的最左节点
while (min->_left)
{
minParent = min;
min = min->_left;
}
//将找到的值和cur交换
swap(cur->_key, min->_key);
//如果查找到的最大值或者最小值 还有另外的节点 需要链接
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
min = nullptr;
}
return true;
}
}
//没有删除返回false
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}//为了预防数据被改,我们改成privat
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << endl;
_InOrder(root->_right);
}
Node* _root = nullptr;
};
四 .Key-Value模型的搜索二叉树
如果说key模型的值只能表示在不在的话,那么key-value模型就可以表示通过一个值查找另一个值。
key value模型 与上述实现的二叉搜索树实现功能差不多,只是增加了一个模板参数value。
代码为:
template<class K, class V>
class BSTreeNode
{
public:
BSTreeNode(const K& key=K(),const V& val=V())
:_left(nullptr)
,_right(nullptr)
,_key(key)
,_val(val)
{
}
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _val;
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node; //这里我们重命名一下,节省代码
public:
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
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
{
return false;
}
}
cur = new Node(key, value);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
Node* Find(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
{
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
{
//开始删除
//一共有三种情况
if (cur->_left == nullptr)
{
if (_root == cur)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_left = cur->_right;
}
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)
{
if (_root == cur)
{
_root = cur->_left;
}
else
{
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
else
{
Node* minParent = cur;
Node* min = cur->_right;
while (min->_left)
{
minParent = min;
min = min->_left;
}
swap(cur->_key, min->_key);
if (minParent->_left == min)
minParent->_left = min->_right;
else
minParent->_right = min->_right;
delete min;
min = nullptr;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << ":" << root->_val<< endl;
_InOrder(root->_right);
}
Node* _root = nullptr;
};