1.前言
二叉搜索树是二叉树进阶中最基础的一种树,其特点是:非空左子树的所有键值小于其根节点的键值;非空右子树的所有键值大于其根节点的键值;左/右子树都是二叉搜索树。通过这些性质不难发现,二叉搜索树最大的特性就是其中序遍历有序的,而且二叉搜索树不允许相同节点的出现,这也起到了去重的作用。
它还有两个重要的结论:
1.整棵树中最小的节点就是整棵树最左的节点;
2.整棵树中最大的节点就是整棵树最右的节点。
这两个结论对于我们实现二叉搜索树具有重要的基础。
2.实现
那么我们该如何实现一颗搜索二叉树呢?首先,对于实现搜索二叉树,我们主要实现它的查找、插入和删除,还是老规矩,代码和注释都在下面啦,有疑问的欢迎留言~
(tip:如果有没有注释的,先上面总结的结论,然后结合代码,应该能独立想出来)
#pragma once
#include<iostream>
using namespace std;
template<class K>
struct BSTNode
{
K _key; // 节点数据
BSTNode<K>* _left; // 左孩子
BSTNode<K>* _right; // 右孩子
BSTNode(const K& key)
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
};
template<class K>
class BSTree
{
using Node = BSTNode<K>;
public:
bool Insert(const K& key)
{
// 如果root为空,root直接是key
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
return false;
}
// 到这里的时候,cur是nullptr,所以而parent就是要插入的地方的父亲,所以只需要判断是插到左边还是右边即可
cur = new Node(key);
if (cur->_key > parent->_key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
bool 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 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
{
// 找到了要删除的节点
// 1.被删除节点只有左孩子或者没有孩子,且被删除节点是它父亲的左孩子
if (cur->_right == nullptr && parent->_left == cur)
{
parent->_left = cur->_left;
delete cur;
}
// 2.被删除节点只有左孩子或者没有孩子,且被删除节点是它父亲的右孩子
else if (cur->_right == nullptr && parent->_right == cur)
{
parent->_right = cur->_left;
delete cur;
}
// 3.被删除节点只有右孩子或者没有孩子,且被删除节点是它父亲的左孩子
else if (cur->_left == nullptr && parent->_left == cur)
{
parent->_left = cur->_right;
delete cur;
}
// 4.被删除节点只有右孩子或者没有孩子,且被删除节点是它父亲的右孩子
else if (cur->_left == nullptr && parent->_right == cur)
{
parent->_right = cur->_right;
}
// 5.被删除节点有两个孩子
else
{
// 可以有两种选择:1.选被删除节点的右孩子的最左节点。2.选择被删除节点的左孩子的最右节点
// 这里我们选择右孩子的最左节点
Node* son = cur->_right;
Node* sonparent = cur; // 这里不能初始化为nullptr,因为如果son没有左孩子,则下面的循环就进不去,sonparent就永远是nullptr,就会出现野指针,而一开始son的父亲就是cur,所以sonparent初始化为cur可以避免空指针
while (son->_left != nullptr)
{
sonparent = son;
son = son->_left;
}
cur->_key = son->_key;
// 此时son就是右孩子的最左节点,现在需要替代被删除节点
// sonparent->_left = son->_left ? son->_left : son->_right;
// 因为son是经过检查后是最左的节点,所以son不可能有左孩子,只可能有右孩子,所以不用进行上面的判断
if (sonparent->_left == son)
sonparent->_left = son->_right;
else
sonparent->_right = son->_right;
delete son;
}
return true;
}
}
return false;
}
// 中序遍历(搜索二叉树的中序遍历就是有序的)
void InOrder()
{
_InOrder(_root);
}
private:
// 如果不写一个子函数,则如果我们要在test.cpp进行中序遍历时,无法传到_root,因为它是私有的,所以我们写成子函数,然后外面再套一个,类里面的可以访问到私有的,然后外面的可以访问到共有的函数,这样就实现了嵌套访问
// 而且这里也不能用缺省值传,因为缺省值只能用常量和全局变量,要访问成员变量一定要用this指针,这个位置没有this指针
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ' ';
_InOrder(root->_right);
}
Node* _root = nullptr;
};