二叉搜索树(Binary Search Tree,BST),也称为二叉查找树或二叉排序树,是一种特殊的二叉树,其中节点的值按照一定规律排列。具体来说 ,对于任意一个节点,其左子树的节点键值均小于根节点的键值;右子树的节点键值均大于根节点的键值。
即满足以下性质:
下面的代码是二叉搜索树的代码实现框架,省去了具体功能的实现。
#pragma once
namespace aiyimu
{
template<class K>
// 二叉树节点
struct BSTreeNode
{};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
// 尾插
bool insert(const K& key)
{}
// 中序遍历
void inOrder()
{}
// 查找 值为key的节点
bool Find(const K& key)
{}
// 删除 值为key的节点
bool Erase(const K& key)
{}
// ---------------------------------------- 递归写法 ---------------------------------------
// 查找
bool FindR(const K& key)
{}
bool InsertR(const K& key)
{}
bool EraseR(const K& key)
{}
// 默认成员函数
// 构造
BSTree() = default;//C++: 强制编译器生成默认构造
// 拷贝构造
BSTree(const BSTree<K>& bst)
{}
// 析构
~BSTree()
{}
// 赋值重载
BSTree<K>& operator=(BSTree<K> bst)
{}
// 不能访问_root
private:
// 拷贝
Node* _Copy(Node* root)
{}
// 销毁BST
void _Destory(Node*& root)
{}
bool _FindR(Node* root, const K& key)
{}
bool _InsertR(Node* root, const K& key)
{}
bool _EraseR(Node* root, const K& key)
{}
void _inOrder(Node* root)
{}
private:
Node* _root = nullptr;
};
}
template<class K>
// 二叉树节点
struct BSTreeNode
{
// 成员变量
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
// 构造函数
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
_left
表示指向当前节点的左子节点的指针;_right
表示指向当前节点的右子节点的指针;_key
表示当前节点所存储的键值(key),即二叉搜索树中的排序关键字。_left 和 _right 成员变量的类型均为 BSTreeNode*,即指向某个类型为 K 的节点的指针。这是因为二叉树节点本身也是一种自定义数据类型,其成员变量也可以是指向其他节点的指针。
构造函数
// 尾插
bool insert(const K& key)
{
// 如果此时无节点,直接创建新节点作为_root,返回true
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
// 查找要插入的位置
while (cur)
{
// 如果key大,则向右插入
// 反之key小向左插入
if (key > cur->_key)
{
//parent存cur:代表改变后的cur的父节点
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
// 元素重复则返回false
else
{
return false;
}
}
// 此时找到了待插入的位置cur
// 将key插入作为cur的左/右子节点
//创建节点
cur = new Node(key);
if (key > parent->_key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true; //插入成功返回true
}
在上文框架重可以知道,inOrder()
是public访问,而_inOrder()
是private访问
好处:
public:
void inOrder()
{
_inOrder(_root);
cout << endl;
}
private:
void _inOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_inOrder(root->_left);
cout << root->_key << " ";
_inOrder(root->_right);
}
bool Find(const K& key)
{
Node* cur = _root;
// 遍历二叉搜索树查找key
while (cur)
{
// 如果key大,则向右找
if (key > cur->_key)
{
cur = cur->_right;
}
// key小,向左找
else if (key < cur->_key)
{
cur = cur->_left;
}
// 找到了
else
{
return true;
}
}
// 退出循环,找不到
return false;
}
找到节点后,执行删除操作,一共有三种情况:
下图做解释:
当左树为空 且 待删除的节点是根节点
当左树为空 且 待删除的节点不是根节点
当右树为空
当右树为空的操作思路和左树为空时一致,按照左树的思路写即可。
当左右树都不为空
另外:在这段代码中,由于是在查找 cur 节点并删除其子树的过程中,无论 cur 是否是根节点都不会影响查找和删除的过程。
bool Erase(const K& key)
{
Node* parent = nullptr;
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
{
// 三种情况
// 1、左为空
// 2、右为空
// 3、左右都不为空
// 1. 左为空时
if (cur->_left == nullptr)
{
// 当要删除的为根时,parent会出问题,单独写这种情况
if (cur == _root)
{
_root = cur->_right; // 直接将根改为右节点(左为空)
}
// 判断 cur 在 parent 的左侧还是右侧,然后将 parent 对应的子节点指向 cur 的右子节点。
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
// 清理掉 cur 的内存空间
delete cur;
cur = nullptr;
}
// 2. 右为空时
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
}
// 3. 左右都不为空
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;
}
return true;
}
}
return false;
}
这里使用了 C++11 中的新特性:= default
它表示对于该函数,我们采用编译器默认生成的实现方式,而不需要手写构造函数的实现代码。
直接用默认构造进行成员变量的初始化
BSTree() = default;//C++: 强制编译器生成默认构造
拷贝构造依然调用_Copy()私有函数,传入bst._root
BSTree(const BSTree<K>& bst)
{
_root = _Copy(bst._root);
}
_Copy()
// 拷贝
Node* _Copy(Node* root)
{
// root为空返回空
if (root == nullptr)
{
return nullptr;
}
// 拷贝节点和指向关系
Node* copyRoot = new Node(root->_key);
copyRoot->_left = _Copy(root->_left);
copyRoot->_right = _Copy(root->_right);
return copyRoot;
}
_Destory()
,传入_rootpublic:
~BSTree()
{
_Destory(_root);
}
private:
void _Destory(Node*& root)
{
if (root == nullptr)
return;
// 递归销毁根节点所有左右节点
_Destory(root->_left);
_Destory(root->_right);
// 销毁根节点
delete root;
root = nullptr;
}
函数体中调用了 swap
函数,将当前对象的 根节点 _root 和新建的对象 bst 的根节点 bst._root 进行交换 ,从而实现了二叉搜索树的值拷贝和赋值操作
BSTree<K>& operator=(BSTree<K> bst)
{
swap(_root, bst._root); // 交换根节点
return *this;
}
bool _InsertR(Node* root, const K& key)
{
// 为空,直接创建新节点
if (root == nullptr)
{
root = new Node(key);
return true;
}
// 查找位置并插入节点
if (key > root->_key)
{
return _InsertR(root->_right, key);
}
else if (key < root->_key)
{
return _InsertR(root->_left, key);
}
else
return false;
}
相同的思路,递归查找
bool _FindR(Node* root, const K& key)
{
// 遇空返回
if (root == nullptr)
{
return;
}
// 如果key大向右找
if (key > root->_key)
return _FindR(root->_right, key);
else if (key < root->_key)
return _FindR(root->_left, key);
else
return true;
}
bool _EraseR(Node* root, const K& key)
{
// 为空返回false
if (root == nullptr)
{
return false;
}
// 寻找要删除的节点
if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if (key < root->_left)
{
return _EraseR(root->_left, key);
}
else
{
// 执行删除操作
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_right;
}
else
{
// 找到右树的最 小/左 节点并替换
Node* min = root->_right;
while (min->_left)
{
min = min->_left;
}
swap(root->_key, min->_key);
return _EraseR(root->_right, key);
}
// 删除节点
delete del;
return true;
}
}