在数据结构中,我们对二叉树这个数据结构有了基本的了解,并对其的部分操作进行学习和实现,在那部分,我们对递归也进行了充分的利用。 本节我们将学习一种新的二叉树结构,叫做二叉搜索树,就如其名字一样,它的功能在于查找搜索。下面我们将进行系统的学习。
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
若它的右字树不为空,则右子树上所有结点的值都大于等于根结点的值
它的左右子树也分别为二叉搜索树
二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等
值,multimap/multiset支持插入相等值。

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为: O(log2 N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为: O(N/2 )
综合而言二叉搜索树增删查改时间复杂度为: O(N)
显然这样的效率显然是无法满足需求的,后续二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于在内存中存储和搜索数据。
二分查找也可以实现 O( logN) 级别的查找效率,但是二分查找有两大缺陷:
1. 需要存储在支持下标随机访问的结构中,并且有序。
2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

接下来,我们将对二叉树的基本操作进行实现。
首先,我们创建一个节点类,然后再创建树。
template <class K>
struct Node {
K _key;
Node<K>* _left;
Node<K>* _right;
Node(const K& key)
:key(Key), _left(nullptr), _right(nullptr)
{}
};
template<class K>
class BSTree {
//typedef Node<K> Node;
using Node = Node<k>;//C++11
public:
private:
Node *_root=nullptr;
};插入的具体过程如下:
1. 树为空,则直接新增结点,赋值给root指针
2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。( 要注意的是要保持逻辑一致性,插⼊相等的值不要一会往右走,一会往左走)
下面代码是不支持插入相同值的,若要插入相同值,改下逻辑即可。
bool Insert(const K& key) {
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;
}while (cur) {
parent = cur;
if (key < cur->_key) {
cur = cur->_left;
}
else { // 如果键值相同或更大,则向右移动
cur = cur->_right;
}
}首先我们要找到插入节点的父亲的位置,这时候再根据大小插入到左右节点。


从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。 最多查找高度次,走到到空,还没找到,这个值不存在。
如果不支持插入相等的值,找到x即可返回
如果支持插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。如下图,查找3,要找到1的右孩子的那个3返回

bool Find(const K& key) {
if (_root == nullptr)
return false;
Node* cur = _root;
while (cur) {
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
Node* findX(int key) {
Node* cur = _root;
Node* firstX = nullptr;
while (cur) {
if (key<cur->_key) {
cur = cur->_left;
}
else if (key > cur->_key) {
cur = cur->_right;
}
else {
// 找到了一个值为x的节点
firstX = cur; // 先记录下这个节点
cur = cur->_left; //cur->_right;
}
}
// 返回第一个出现的值为x的节点
return firstX;
}首先查找元素是否在二叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)
1. 要删除结点N左右孩子均为空
2. 要删除的结点N左孩子位空,右孩子结点不为空
3. 要删除的结点N右孩子位空,左孩子结点不为空
4. 要删除的结点N左右孩子结点均不为空
对应以上四种情况的解决方案:
1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)
2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点。
3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点。
4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点 R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意一个,放到N的 位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。



bool Erase(const K& key) {
Node* cur = _root;
Node* parent = nullptr;
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 (cur == _root) {
_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 (cur == _root) {
_root = cur->_left; // 更新根节点
} else {
if (parent->_left == cur) {
parent->_left = cur->_left;
} else {
parent->_right = cur->_left;
}
}
delete cur;
return true;
} else {
// 节点左右孩子均不为空,找到右子树中的最小节点
Node* replaceParent = cur;
Node* replace = cur->_right;
while (replace->_left) {
replaceParent = replace;
replace = replace->_left;
}
cur->_key = replace->_key; // 用右子树中最小节点的值替换当前节点的值
if (replaceParent->_left == replace) {
replaceParent->_left = replace->_right;
} else {
replaceParent->_right = replace->_right;
}
delete replace; // 删除替换节点
return true;
}
}
}
return false; // 没有找到要删除的节点
}在代码中我们还考虑了删除的节点就为根节点的情况,而且只有一个分支,就直接更新节点指向。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template <class K>
struct Node {
K _key;
Node<K>* _left;
Node<K>* _right;
Node(const K& key)
:_key(key), _left(nullptr), _right(nullptr)
{}
};
template<class K>
class BSTree {
//typedef Node<K> Node;
using Node = Node<K>;
public:
bool Insert(const K& key) {
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;
}
bool Find(const K& key) {
if (_root == nullptr)
return false;
Node* cur = _root;
while (cur) {
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
bool Erase(const K& key) {
Node* cur = _root;
Node* parent = nullptr;
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 (cur == _root)
_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 (cur == _root)
_root = cur->_left;
else {
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
//节点左右孩子均不为空
else {
//我们采用右子树最小(最左)
Node* replaceParent = cur;
Node* replace = cur->_right;
while (replace->_left) {
replaceParent = replace;
replace = replace->_left;
}
cur->_key = replace->_key;
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
return true;
}
}
}
return false;
}
//Node* findX(int key) {
// Node* cur = _root;
// Node* firstX = nullptr;
// while (cur) {
// if (key<cur->_key) {
//
// cur = cur->_left;
// }
// else if (key > cur->_key) {
//
// cur = cur->_right;
// }
// else {
// // 找到了一个值为x的节点
// firstX = cur; // 先记录下这个节点
// cur = cur->_left; //cur->_right;
// }
// }
// // 返回第一个出现的值为x的节点
// return firstX;
//}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root) {
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node *_root=nullptr;
};
int main() {
BSTree<int>t;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
t.Insert(e);
t.InOrder();
t.Insert(16);
//t.Insert(3);
t.InOrder();
if (t.Find(5)){
cout << "找到了" << endl;
}
t.Erase(3);
t.InOrder();
t.Erase(8);
t.InOrder();
for (auto e : a) {
t.Erase(e);
t.InOrder();
}
return 0;
}本节内容就到此结束了,下节内容我们将简单列举二叉搜索树的使用场景。 最后感谢各位友友的支持!!!