今天,我们继续踏入追寻C++的冒险历程。上一章我们简单介绍并且了解了C++三大特性之一的多态,至此关于C++的三大特性已经介绍完了。那么本章将为大家讲解一种特殊的数据结构——二叉搜索树,以便为后面章节的学习打下基础。下面让我们一起来进入二叉搜索树的学习。
二叉搜索树是一棵特殊的二叉树,从名字上我们可以看出这颗特殊的二叉树的特殊点就在于搜索二字。接下来我们先了解一下什么是二叉搜索树,再看一看二叉搜索有什么作用。
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:
下面我们来看一颗颗二叉搜索树:
在上面这棵二叉搜索树中我们可以观察到,无论对于哪个结点,它左子树中的所有值都是小于该结点的值,右子树中的所有值都是大于该结点的值。在⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我们学习map/set/multimap/multiset
系列容器底层就是⼆叉搜索树,其中map/set
不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值
那么二叉搜索树有什么作用呢?第一个自然是搜索,当我们在一颗二叉搜索树中去寻找某个值时,它的时间复杂度只与这棵树的高度有关,这是因为当我们需要查找的值如果小于当前结点,那么只需要去它的左子树中去找,反之只需要去它的右子树中寻找。我们来具体的分析一下:
所以根据时间复杂度的计算要求而言,二叉搜索树的查找时间复杂度为 O(N);那么这样的效率自然是无法满足我们的需求,毕竟这跟一遍遍历没有什么区别,但是这是在二叉搜索树的高度过高的情况下,那么为了避免二叉搜索树的高度过高,能使它接近于完全二叉树的形态,就有了一种基于二叉搜索树,对其进行变形所形成的一种新的数据结构——AVL树(平衡二叉搜索树)和红黑树。它们解决了二叉搜索树有可能高度过高的缺陷,适⽤于我们在内存中存储和搜索数据。 我们现在所讲的二叉搜索树就是为后面学习AVL树和红黑树以及set和map这两个容器打下基础。
说到查找,相信大家第一个想到的快速查找的方法就是二分查找,虽然二分查找也能实现**O(log2N)**级别的查找效率,但是二分查找有两大缺陷:
光是有序一条便使得二分查找算法在对无序的元素进行查找时需要先进行排序。这里便体现出了AVL树(平衡二叉搜索树)的价值。
上面介绍了查找,二叉搜索树其实还有着排序的作用,认真观察上面的二叉搜索树不难发现,当我们对这棵树进行中序遍历时,便是得到了一个升序的排列。
下面我们先来了解二叉搜索树的增删查操作,看一下它的原理,为后面的学习打下基础。至于为什么没有改这个操作是因为搜索二叉树对于每个结点的值都是有要求的,不能随意更改,不然就会破坏二叉搜索树的性质。
插⼊的具体过程如下:
root
指针。下面我们通过两张张图来感受一下二叉搜索树的插入:
由于要把结点插入,我们需要用父节点指向需要插入的结点,因此在设计代码时需要注意要用一个变量去保存父节点的值。下面我们来看一下如何自己用代码去实现(不允许插入相同值):
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
BSTreeNode(K key)
:_key(K)
,_left(nullptr)
,_right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//...
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if(key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (key > parent->_key)
{
parent->_right = new Node(key);
}
else
{
parent->_left = new Node(key);
}
return true;
}
//...
private:
Node* _root = nullptr;
};
二叉搜索树的查找原理非常简单:从根开始⽐较,例如查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找。 最多查找⾼度次,若是⾛到空位置,还没找到,则证明这个值不存在。
需要特别注意的是:
下面我们来看一下如何自己用代码去实现(不允许插入相同值):
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
BSTreeNode(K key)
:_key(K)
,_left(nullptr)
,_right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//...
bool find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
//...
private:
Node* _root = nullptr;
};
要在二叉搜索树中删除元素,⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false。其次,还要确保删除该元素后不破坏二叉搜索树的性质。如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)
对应以上四种情况的解决⽅案:
下面我们来看一下如何自己用代码去实现(不允许插入相同值):
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
BSTreeNode(K key)
:_key(K)
,_left(nullptr)
,_right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//...
bool erase(const K& key)
{
if (_root == nullptr)
{
return false;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//0-1个孩⼦的情况,删除情况1 2 3均可以直接删除,改变⽗亲对应孩⼦指针指向即可
if (cur->_left == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (key > parent->_key)
{
parent->_right = cur->_right;
}
else
{
parent->left = cur->_right;
}
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (key > parent->_key)
{
parent->_right = cur->_left;
}
else
{
parent->left = cur->_left;
}
}
delete cur;
return true;
}
//2个孩⼦的情况删除情况4,替换法删除
//假设这⾥我们取左⼦树的最大结点作为替代结点去删除
else
{
Node* maxleft = cur->left;
Node* maxleftParent = cur;
while (maxleft->_right)
{
maxleftParent = maxleft;
maxleft = maxleft->_right;
}
cur->_key = maxleft->_key;
if (key > maxleftParent->_key)
{
maxleftParent->_right = maxleft->_left;
}
else
{
maxleftParent->_left = maxleft->_left;
}
}
return true;
}
return false;
}
}
//...
private:
Node* _root = nullptr;
};
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。
⼆叉搜索树key和key/value使⽤场景分别对应着两种容器:set和map ,也就是我们下一章所要讲解的内容,本章讲解二叉搜索树便是为了后面的章节做一个铺垫。
上面我们所讲的代码都是key二叉搜索树,下面是key/value二叉搜索树的代码,感兴趣的可以了解一下:
template<class K, class V>
struct BSTNode
{
// pair<K, V> _kv;
K _key;
V _value;
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
BSTNode(const K& key, const V& value)
:_key(key)
, _value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
public:
BSTree() = default;
BSTree(const BSTree<K, V>& t)
{
_root = Copy(t._root);
}
BSTree<K, V>& operator=(BSTree<K, V> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
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* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
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 (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* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
} c
ur->_key = rightMin->_key;
if (rightMinP->_left == rightMin)
rightMinP->_left = rightMin->_right;
else
rightMinP->_right = 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->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
Node* _root = nullptr;
}
若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!