Bootstrap

超级详细的BSTree--底层代码实现

二叉搜索树完整C++实现

目录

  1. 引言
  2. 二叉搜索树定义
  3. BSTree.hpp 文件
  4. BSTree.cpp 文件
  5. 全部代码

1. 引言

如果你不想,看分块代码,你可以直接划到文末,查看完整代码,注释比较全面。

二叉搜索树(Binary Search Tree, BST)是一种非常重要的数据结构,广泛应用于需要高效查找、插入和删除操作的场景中。本文将通过C++实现完整的二叉搜索树,并拆分成头文件和源文件,便于工程化管理。同时我们将加入详细的注释与优化,使代码更加丰富、可维护性更高。

2. 二叉搜索树定义

二叉搜索树(BST) 是一种特殊的二叉树,满足以下性质:

  1. 对于每个节点,左子树中所有节点的值都小于该节点的值;
  2. 对于每个节点,右子树中所有节点的值都大于该节点的值;
  3. 左右子树本身也是二叉搜索树。

这种性质使得查找、插入和删除操作的平均时间复杂度为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;
}
;