二叉搜索树完整C++实现
目录
1. 引言
如果你不想,看分块代码,你可以直接划到文末,查看完整代码,注释比较全面。
二叉搜索树(Binary Search Tree, BST)是一种非常重要的数据结构,广泛应用于需要高效查找、插入和删除操作的场景中。本文将通过C++实现完整的二叉搜索树,并拆分成头文件和源文件,便于工程化管理。同时我们将加入详细的注释与优化,使代码更加丰富、可维护性更高。
2. 二叉搜索树定义
二叉搜索树(BST) 是一种特殊的二叉树,满足以下性质:
- 对于每个节点,左子树中所有节点的值都小于该节点的值;
- 对于每个节点,右子树中所有节点的值都大于该节点的值;
- 左右子树本身也是二叉搜索树。
这种性质使得查找、插入和删除操作的平均时间复杂度为O(log n),在最坏情况下(树退化成链表)复杂度为O(n)。
3. BSTree.hpp 文件
头文件中我们将声明二叉搜索树类的基本结构和操作。头文件主要用于类的声明和接口定义,具体实现放在 .cpp
文件中。
BSTree.hpp
#ifndef BSTREE_HPP
#define BSTREE_HPP
#include <iostream>
// 命名空间 YU 内封装二叉搜索树的实现
namespace YU
{
// 模板结构体定义,代表二叉搜索树的节点
template <class K>
struct BSTreeNode
{
K _key; // 节点保存的键值
BSTreeNode<K> *_left, *_right; // 左右子节点指针
// 构造函数,初始化节点
BSTreeNode(const K &key)
: _key(key), _left(nullptr), _right(nullptr) {}
};
// 二叉搜索树类定义
template <class K>
class BSTree
{
public:
// 插入节点
bool Insert(const K &key);
// 查找节点
bool Find(const K &key);
// 删除节点
bool Remove(const K &key);
// 中序遍历
void InOrder() const;
// 构造函数与析构函数
BSTree();
~BSTree();
private:
BSTreeNode<K> *_root; // 根节点指针
// 递归帮助函数
void _InOrder(BSTreeNode<K> *node) const;
void _Destroy(BSTreeNode<K> *node);
};
}
#endif // BSTREE_HPP
头文件解读
- BSTreeNode 结构体:节点结构,包含键值
_key
和左右子树指针_left
、_right
。采用模板类,支持任意类型的键值。 - BSTree 类:封装二叉搜索树的各种操作,如插入、查找、删除及中序遍历。核心操作在私有成员函数中实现。
- 递归帮助函数:如
_InOrder
用于递归中序遍历,_Destroy
用于递归销毁树。
4. BSTree.cpp 文件
源文件中我们将实现二叉搜索树的所有功能,包括插入、查找、删除和遍历操作。
4.1 插入操作
插入操作需要遍历树找到合适位置,遵循二叉搜索树的规则。具体代码如下:
BSTree.cpp
#include "BSTree.hpp"
namespace YU
{
// 构造函数,初始化根节点为空
template <class K>
BSTree<K>::BSTree() : _root(nullptr) {}
// 析构函数,销毁树
template <class K>
BSTree<K>::~BSTree()
{
_Destroy(_root);
}
// 递归销毁树
template <class K>
void BSTree<K>::_Destroy(BSTreeNode<K> *node)
{
if (node)
{
_Destroy(node->_left);
_Destroy(node->_right);
delete node;
}
}
// 插入操作
template <class K>
bool BSTree<K>::Insert(const K &key)
{
// 根节点为空,直接插入根节点
if (_root == nullptr)
{
_root = new BSTreeNode<K>(key);
return true;
}
// 遍历找到插入位置
BSTreeNode<K> *cur = _root;
BSTreeNode<K> *parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
// 已存在该键值
return false;
}
}
// 插入节点
if (key < parent->_key)
{
parent->_left = new BSTreeNode<K>(key);
}
else
{
parent->_right = new BSTreeNode<K>(key);
}
return true;
}
4.2 查找操作
查找操作沿着树遍历,逐步比较节点值,直到找到目标节点或树遍历完。代码如下:
// 查找操作
template <class K>
bool BSTree<K>::Find(const K &key)
{
BSTreeNode<K> *cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return true; // 找到目标节点
}
}
return false; // 未找到
}
4.3 删除操作
删除操作涉及到三种情况:删除叶子节点,删除仅有一个子节点的节点,以及删除有两个子节点的节点。代码如下:
// 删除操作
template <class K>
bool BSTree<K>::Remove(const K &key)
{
BSTreeNode<K> *cur = _root;
BSTreeNode<K> *parent = nullptr;
// 查找目标节点
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
{
break; // 找到目标节点
}
}
// 如果未找到节点
if (cur == nullptr)
{
return false;
}
// 处理删除节点
if (cur->_left == nullptr)
{
// 节点无左子树
if (cur == _root)
{
_root = cur->_right;
}
else if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
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;
}
}
else
{
// 节点有两个子节点
BSTreeNode<K> *replace = cur->_right;
BSTreeNode<K> *replaceParent = cur;
// 找到右子树中的最小节点
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
// 替换当前节点的值
cur->_key = replace->_key;
// 删除替换节点
if (replace == replaceParent->_left)
{
replaceParent->_left = replace->_right;
}
else
{
replaceParent->_right = replace->_right;
}
cur = replace; // 将删除的节点设置为替换节点
}
delete cur
5.全部代码
在此说明上面的内容全部基于我的代码+GPT润色。如果你不喜欢看分块的代码,那么下面有着完成的代码。
5.1头文件:BSTree.hpp
//BSTree.hpp
#pragma once
#include<iostream>
#include<string>
using namespace std;
namespace YU
{
template<class K> //写模板的时候不需要写冒号
//首先我们先要完成一个搜素二叉树的一个节点的结构体
//一个新节点需要包含指向左右孩子指针,以及存放键值
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;//键值
BSTreeNode(const K& key)//通过初始化方式来初始化成员变量
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//构造函数
BSTree()
:_root(nullptr)
{}
//拷贝构造
BSTree(const BSTree<K>& t)
{
_root = Copy(t._root);
}
//重载赋值构造
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
//删除函数
void Destroy(Node* node)
{
if (node != nullptr)
{
Destroy(node->_left);
Destroy(node->_right);
delete node;
}
}
//重点:插入函数
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
{
cout << "已存在相同数字" << endl;
return false;
}
}
//遇到重复跳出循环
//上面while循环,遇到cur的左/右指针为空的时候,跳出循环,此时cur为空位置,parent为cur父母节点
cur = new Node(key);//走到这里一定是会有一个符合的key,否则之前已经跳出循环
//在这一步,虽然已经new出一个节点,但是还没有和父母节点连接起来
//比较键值,连接父母节点
if (parent->_key < 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
{
//每次循环往下走一层,直到找到对应的数字,返回true
//如果没有找到cur在上一次的循环会被赋予空,不会进入下一次的循环
return true;
}
return false;
}
}
//重点,难点
bool Erase(const K& key)
{
if (_root == nullptr)
{
return false;
}
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
{
//走到这一步的时候目标节点已经找到,下一步应该将parent节点与下一节点连接起来
//cur节点有多种情况 1.左为空 2.右为空 3.左右都为空 4. 左右都不为空
//3可以和1或2合并
//1.当左为空的时候,parent直接与cur右孩子连起来
//2.当右为空的时候,parent直接与cur左孩子连起来
//但是parent与其他节点连起来的时候,前提是parent是一个节点即非空
//1.左为空(包含左右都为空)
if (cur->_left == nullptr)
{
if (cur == _root)//等价于parent == nullptr
{
_root = _root->_right;
}
else
{
//需要找到cur是parent的左/右孩子
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
}//2.右为空时
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = _root->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
else//3.当左右孩子不为空时
{
//方法:拿cur左孩子的最大节点(不一定是右下角,一定是最右侧)替换cur。或者拿右孩子最小的节点来替换cur(不一定是左下角,一定是最左侧)
//我们现在开始找右孩子最小的一个节点,只需要一直找左孩子就行(除了第一步)直到为空,同时要拿parent来接
Node* parent = cur;
Node* minRight = cur->_right;
while (minRight->_left)//这里可以不加left,无论加不加minright都是右子树的最小值
{
parent = minRight;
minRight = minRight->_left;
}
cur->_key = minRight->_key;
//下面部分比较难以理解。首先,我们不知道minright是否存在右孩子(不会有左孩子),删minright时需要把其右孩子连接到父节点上
if (minRight == parent->_left)//当右子树最小部分是父节点的左孩子时(大概率是这个情况)
{
parent->_left = minRight->_right;//我们将minright的右孩子连接到父节点的左孩子
}
else//一般来说minright只会出现在左子树上,但是也会有例外,右子树数据呈递增分部,也就形成单一右子树
{
parent->_right = minRight->_right;
}
delete minRight;
}
return true;
};
};
};
//中序遍历
void InOrder(BSTreeNode<K>* root)
{
if (root == nullptr)
return;
InOrder(root->_left);
cout << root->_key << " ";
InOrder(root->_right);
}
// 中序遍历打印树
void PrintInOrder() {
InOrder(_root);
cout << endl;
}
private:
Node* _root = nullptr;
//这里是class BSTree
};
//这里是namespace
};
5.2源文件:BSTree.cpp
#include "BSTree.h" // 假设这是你的BSTree类的头文件路径
#include<vector>
using namespace std;
using namespace YU;
// 测试二叉搜索树的功能
void TestBSTree()
{
BSTree<int> bst;
// 测试插入
vector<int> keys = { 8, 3, 1, 6, 4, 7, 10, 14, 13 };
for (const auto& key : keys)
{
cout << "插入: " << key << endl;
bst.insert(key);
}
//遍历
cout << "打印一个bst" << endl;
bst.PrintInOrder();
// 测试查找
for (const auto& key : keys)
{
cout << "寻找: " << key << " -> ";
cout << (bst.Find(key) ? "找到" : "没找到") << endl;
}
// 测试删除
for (const auto& key : keys)
{
cout << "删除 : " << key << endl;
bst.Erase(key);
}
// 再次尝试查找已被删除的键值
for (const auto& key : keys)
{
cout << "删除后查找键值: " << key << " -> ";
cout << (bst.Find(key) ? "找到" : "没找到") << endl;
}
}
int main()
{
TestBSTree();
return 0;
}