当前位置: 首页 > news >正文

【C++】二叉搜索树及其模拟实现

目录

  • 一、核心概念
    • 1.1 基本定义
    • 1.2 二叉搜索树的性能分析
    • 1.3 基本框架搭建
      • 1.3.1 结点结构的定义
      • 1.3.2 二叉搜索树的类框架
  • 二、二叉搜索树的模拟实现
    • 2.1 插入 insert
    • 2.2 中序遍历 InOrder
    • 2.3 查找操作 Find
    • 2.4 删除操作
    • 2.5 插入的递归写法
  • 三、<key, value> 的应用场景
    • 3.1 应用场景

在这里插入图片描述
个人主页<—请点击
C++专栏<—请点击

一、核心概念

1.1 基本定义

二叉搜索树是一种特殊的二叉树,满足以下性质:左子树所有节点的值小于根节点的值右子树所有节点的值大于根节点的值左右子树也都是二叉搜索树
在这里插入图片描述
如上图所示,就是一颗二叉搜索树。值得一提的是它的中序遍历是有序的,对于上面的树:1 3 4 6 7 8 10 13 14

1.2 二叉搜索树的性能分析

查找操作:最佳情况:平衡树,O(log n),最坏情况:退化成链表,O(n),平均情况:随机数据,O(log n)

插入操作的时间复杂度与查找相同,需要额外的节点创建开销。

删除操作:最复杂的操作,有三种情况,叶子节点:直接删除;一个子节点:用子节点替代;两个子节点:找合适的结点替代。时间复杂度:O(h)h为树高。

二叉搜索树在平均情况下性能很好O(log n),但最坏情况下会退化为O(n)

1.3 基本框架搭建

1.3.1 结点结构的定义

template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){ }
};

1.3.2 二叉搜索树的类框架

template<class K>
class BSTree
{typedef BSTNode<K> Node;
public:
private:Node* _root = nullptr;
};

二、二叉搜索树的模拟实现

2.1 插入 insert

插入过程分析:树为空,则直接新增结点,赋值给_root指针;树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点;如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点,但要保持方向统一。

我们在实现的时候不允许插入相等的值

bool insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr; //刚开始根节点的父结点为空Node* cur = _root;while (cur){parent = cur; //记录父节点if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else return false;}//此时cur为nullptrcur = new Node(key);if (key > parent->_key) parent->_right = cur;else parent->_left = cur;return true;
}

代码测试

BSTree<int> st;
st.insert(8);
st.insert(3);
st.insert(10);
st.insert(1);
st.insert(6);

测试结果
在这里插入图片描述
如上图,符合插入规则

2.2 中序遍历 InOrder

中序遍历就比较简单了,可以使用递归实现。但是这里有一个问题,中序遍历需要我们传递根节点,但是根节点是私有的,我们不能传递。
在这里插入图片描述
解决方案可以在类中实现一个子函数,调用中序遍历传递根节点

代码实现

	void _InOrder(){InOrder(_root);cout << endl;}private:void InOrder(Node* root){if (root == nullptr){return;}InOrder(root->_left);cout << root->_key << " ";InOrder(root->_right);}

代码测试

void test1()
{BSTree<int> st;st.insert(8);st.insert(3);st.insert(10);st.insert(1);st.insert(1);st.insert(6);st._InOrder();
}

测试结果
在这里插入图片描述

实现了这么多,也可以发现二叉搜索树的效率是不可控的,一旦插入的结点是有序或者接近有序时,树的结构就会接近一个单链表,这就导致查找等时间复杂度接近达到O(n)

2.3 查找操作 Find

查找操作就十分简单了,一次遍历比较一遍就好了。单独一个key结构返回true或者false就可以了。

bool Find(const K& key)
{Node* cur = _root;while (cur){if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;else return true;}return false;
}

2.4 删除操作

修改操作在单个key结构中通常是不被允许的,因为一旦修改,这棵树就极有可能不再是二叉搜索树了。

二叉搜索树的删除操作分为三种情况,第一种就是当前结点没有子节点,此时直接删除就可以;第二种就是有一个子节点,此时需要用子节点替换要删除的结点;第三种就是有两个子节点,此时可以用左子树的最大结点或者右子树的最小节点替换要删除的结点。

前两种都好理解,我们来看第三种:
在这里插入图片描述
我们在实现的时候选择右子树中的最小结点代替,来解决这种情况。

代码实现

bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//找到了,准备删除if (cur->_left == nullptr) //使用cur的右节点代替cur,包含左右子树为空的情况{if (cur != _root){// 左为空,父结点指向cur的右if (cur == parent->_left){parent->_left = cur->_right;}else parent->_right = cur->_right;}else _root = cur->_right; //更换根节点delete cur;}else if (cur->_right == nullptr){if (cur != _root){// 右为空,父结点指向cur的左if (cur == parent->_left){parent->_left = cur->_left;}else parent->_right = cur->_left;}else _root = cur->_left; //更换根节点delete cur;}else //左右子树都不为空{//找到右子树中的最小值代替curNode* minRightParent = cur;Node* minRight = cur->_right;while (minRight->_left){minRightParent = minRight;minRight = minRight->_left;}//此时minRight为cur右子树中的最小结点//此时直接交换cur和minRight中的值_key,//再删除minRightswap(cur->_key, minRight->_key);//处理minRight可能存在的右子树if (minRight == minRightParent->_left)minRightParent->_left = minRight->_right;else minRightParent->_right = minRight->_right;delete minRight;}return true;}}return false;
}

注意:当要删除的cur有两个子节点时,不能直接删除cur结点,这样会导致树的结构断裂。只能用替换法删除

代码测试

void test1()
{BSTree<int> st;st.insert(8);st.insert(3);st.insert(10);st.insert(1);st.insert(1);st.insert(6);st._InOrder();st.Erase(3);st._InOrder();st.Erase(10);st._InOrder();st.Erase(1);st._InOrder();st.Erase(6);st._InOrder();
}

测试结果
在这里插入图片描述

2.5 插入的递归写法

	bool InsertR(const K& key){return _insert(_root, key);}private:bool _insert(Node*& root, const K& key){if (root == nullptr){Node* newNode = new Node(key);root = newNode;return true;}if (key > root->_key)return _insert(root->_right, key);else if (key < root->_key)return _insert(root->_left, key);else return false;}

注意:这里如果_insert的函数参数是Node*,那么就会发生值传递,最终插入操作完成后_root还是会为空

三、<key, value> 的应用场景

场景:需要快速查找、插入、删除键值对实际应用:编程语言中的std::map、std::unordered_map

<key,value>结构中每一个关键码key,都有与之对应的值valuevalue可以是任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value增/删/查还是以key为关键字执行二叉搜索树的规则进行比较,可以快速查找到key对应的valuekey/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树性质了,可以修改value

代码实现:

namespace key_value
{template<class K, class V>struct BSTNode{K _key;V _val;BSTNode<K, V>* _left;BSTNode<K, V>* _right;BSTNode(const K& key, const V& val):_key(key),_val(val), _left(nullptr), _right(nullptr){}};template<class K, class V>class BSTree{typedef BSTNode<K, V> Node;public:~BSTree() //析构函数{_Destroy(_root);}bool insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key, val);return true;}Node* parent = nullptr; //刚开始根节点的父结点为空Node* cur = _root;while (cur){parent = cur; //记录父节点if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else return false;}//此时cur为nullptrcur = new Node(key, val);if (key > parent->_key) parent->_right = cur;else parent->_left = cur;return true;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//找到了,准备删除if (cur->_left == nullptr) //使用cur的右节点代替cur,包含左右子树为空的情况{if (cur != _root){// 左为空,父结点指向cur的右if (cur == parent->_left){parent->_left = cur->_right;}else parent->_right = cur->_right;}else _root = cur->_right; //更换根节点delete cur;}else if (cur->_right == nullptr){if (cur != _root){// 右为空,父结点指向cur的左if (cur == parent->_left){parent->_left = cur->_left;}else parent->_right = cur->_left;}else _root = cur->_left; //更换根节点delete cur;}else //左右子树都不为空{//找到右子树中的最小值代替curNode* minRightParent = cur;Node* minRight = cur->_right;while (minRight->_left){minRightParent = minRight;minRight = minRight->_left;}//此时minRight为cur右子树中的最小结点//此时直接交换cur和minRight中的值_key,//再删除minRightswap(cur->_key, minRight->_key);swap(cur->_val, minRight->_val); //同时交换_val//处理minRight可能存在的右子树if (minRight == minRightParent->_left)minRightParent->_left = minRight->_right;elseminRightParent->_right = minRight->_right;delete minRight;}return true;}}return false;}Node* Find(const K& key) //返回结点{Node* cur = _root;while (cur){if (key > cur->_key)cur = cur->_right;else if (key < cur->_key)cur = cur->_left;elsereturn cur;}return nullptr;}void InOrder(){_InOrder(_root);cout << endl;}bool InsertR(const K& key, const V& val){return _insert(_root, key, val);}private:bool _insert(Node*& root, const K& key, const V& val){if (root == nullptr){Node* newNode = new Node(key, val);root = newNode;return true;}if (key > root->_key)return _insert(root->_right, key, val);else if (key < root->_key)return _insert(root->_left, key, val);elsereturn false;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " " << root->_val << endl;_InOrder(root->_right);}void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}Node* _root = nullptr;};
}

核心改动:对类模板参数增加了value,查找函数不再只是单纯的查找在不在,而是查找完成后返回结点。进行结点删除操作时,如果要删除的结点有两个子节点,再增加交换val的操作。插入函数中插入节点增加了val值。

插入测试

void test2()
{key_value::BSTree<string, string> s;s.insert("conceal", "隐藏");s.insert("claim", "声称");s.insert("fantastic", "极好的");s.insert("forum", "论坛");s.insert("effective", "有效的");s.InOrder();
}

在这里插入图片描述

删除测试

void test3()
{key_value::BSTree<int, int> s;int a[] = { 7,12,3,6,9,2,15,23,21,35,21,11,14,13 };for (auto& e : a){s.insert(e, e);}s.Erase(21);s.Erase(9);s.Erase(7);s.Erase(3);s.InOrder();
}

在这里插入图片描述
查找测试

void test4()
{key_value::BSTree<int, int> s;int a[] = { 7,12,3,6,9,2,15,23,21,35,21,11,14,13 };for (auto& e : a){s.insert(e, e);}auto t = s.Find(21);if (t) cout << t->_val << endl;else cout << "不存在该键值" << endl;auto b = s.Find(16);if (b) cout << b->_val << endl;else cout << "不存在该键值" << endl;
}

在这里插入图片描述

3.1 应用场景

例如统计次数

void test5()
{char c[] = { 'a','b','g','e','w','a','b','c','a','a','b','g' };key_value::BSTree<char, int> s;for (auto& ch : c){auto cur = s.Find(ch);if (!cur) s.insert(ch, 1);else cur->_val++;}s.InOrder();
}

在这里插入图片描述

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

http://www.dtcms.com/a/394599.html

相关文章:

  • 第二十一讲:C++异常
  • 2025年9月第2周AI资讯
  • 从 UNet 到 UCTransNet:一次分割项目中 BCE Loss 失效的踩坑记录
  • leetcode刷题记录2(java)
  • JAVA八股文——方法区
  • 链表操作与反转
  • AI编程 -- 学习笔记
  • 动态规划问题 -- 子数组模型(乘积最大数组)
  • 【AIGC】大模型面试高频考点18-大模型压力测试指标
  • Cannot find a valid baseurl for repo: base/7/x86_64
  • Lowpoly建模练习集
  • 六、kubernetes 1.29 之 Pod 控制器02
  • OpenCV:人脸检测,Haar 级联分类器原理
  • 类和对象 (上)
  • FreeRTOS 队列集(Queue Set)机制详解
  • 【论文速递】2025年第20周(May-11-17)(Robotics/Embodied AI/LLM)
  • 【秋招笔试】2025.09.21网易秋招笔试真题
  • C++ 之 【特殊类设计 与 类型转换】
  • 第14章 MySQL索引
  • Entities - 遍历与查询
  • TargetGroup 全面优化:从六个维度打造卓越用户体验
  • Proxy与Reflect
  • 浅解Letterbox算法
  • 【Triton 教程】triton_language.permute
  • JavaScript洗牌算法实践
  • 掌握timedatectl命令:Ubuntu 系统时间管理指南
  • 【RT Thread】RTT内核对象机制详解
  • Seata分布式事务
  • 用例图讲解
  • makefile原理