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

从解决问题的角度从零实现二插树

引言:二叉树是自我学习c++以来学习的第一个数据结构,其复杂程度与顺序表,链表等数据结构不是一个量级,学习顺序表时,我感觉如鱼得水,甚至产生"编程也没什么大不了的"的想法,即使我忘记,也可以通过查看源代码很快想起;但这次二叉树的实现,狠狠打了我的脸,为了实现这份让我头大的数据结构,我甚至不得不研究了一份解决问题的方法。我意识到了编程比我想的复杂得多,我不过井底之蛙罢了。我必须将我遇到的困难,解决的方法记录下来,由此,我的第一份技术博客诞生了。

解决问题的方法:

遇到问题->将抽象问题具体化->将具体的问题拆分为多个简单问题->逐个击破->复盘

结合二叉树,谈谈我是如何运用方法的

1.抽象问题具体化

从具体到抽象,是我们理解一件事的根本,二叉树也是这样,因此,在我们了解二叉搜索树的概念:根左边的子树全部小于根节点,跟右边的子树全部大于根节点

图1

通过画图的方式,我们成功将抽象的方式具体化了

2.将具体的复杂问题拆分为多个简单问题

需要实现的结构有

A 树的节点 B树

通过以上图,我们可以发现可以实现的树的功能有:

a 插入数据 b 打印数据 c寻找数据 d 删除数据

3.逐个击破

结构:

A 树的节点

template<class K> class BSTnode { using node = BSTnode<K>; public: BSTnode(const K& key) { _key = key; } node* _right=nullptr; node* _left = nullptr; K _key; };

B 树

template<class K> class BSTtree {

a 插入数据

逻辑:将要插入的数据与二叉树的节点值数据对比,小于节点值向左,大于节点值向右,等于不插入,直到当前指针指向的下一个指针为空,我们将值插到空指针

public: using node = BSTnode<K>; void Insert(const K& key) { if (_root == nullptr)特殊:无根树,此时直接插入{ _root = new node(key);//_root是根节点,在b步骤中代码 return; } node* prve = nullptr; node* cur = _root;//用于试探下一次是否为空 while (cur) { if (cur->_key > key) { prve = cur; cur = cur->_left; } else if (cur->_key < key) { prve = cur; cur = cur->_right; } else { return; } }//此时cur使我们要插入的节点1 /*cur= new node(key); if (cur->_key > key) //我的第一个错误,将要插入值的cur用于比较,此时cur- >_key ==key恒成立 { prve->_left = newnode; } else if (cur->_key < key) { prve->_right = newnode; }*/
正确做法:将代插入值的上一节点prve与key比较 node* newnode = new node(key); if (prve->_key > key) { prve->_left = newnode; } else if (prve->_key < key) { prve->_right = newnode; }
b 打印数据为了验证实现的功能,打印作为第二步是最合理的 打印逻辑:传入根并利用中序遍历迭代 中序遍历:一直向左遍历,直到下一节点指向空,打印当前节点并回退到上一节点,并继续向左遍历,循环此动作,直到全部遍历完成
private: void _BSTprint(node* root) { if (root == nullptr) { return; } _BSTprint(root->_left); cout << root->_key << " "; _BSTprint(root->_right); } node* _root = nullptr; };

public:

void BSTprint() { _BSTprint(_root);//只能在类内访问该类的private cout << endl; }

由于不能在对象中访问private的—_root,却可以在类内部访问,因此,我在类内部写一个函数用于调用_BSTprint

c寻找数据由于搜索二叉树本身性质的限制,其不可以修改,因此,寻找基本为删除服务 逻辑:将传入的值与树结点的值比较,小于节点值向左,大于节点值向右,等于便找到了,如果下节点指向空,便没有找到
bool find(const K& key) { //node* prve = nullptr; node* cur = _root; while (cur) { if (cur->_key > key) { cur = cur->_left; } else if (cur->_key < key) { cur = cur->_right; } else { return true; } } return false; }
d 删除数据通过绘制的图,可以发现删除数据会遇到的情况有: 1.待删除数据左右都为空 2.待删除数据左右一边为空

特例:单叉树,如图2


此时,我们如果想要删除根节点,需要将根12变成13

3.待删除数据两边都不为空 可以发现: 两边都为空,其根无论是左边还是右边接入的都是空,插入哪一边都不会影响树的结构 一边为空,我们只需要将它的根插入待删除节点的另一边两边都不为空,由于二叉树的特性,我们不能直接删除,会破坏结构,此时我们需要使用到替换法替换法:将待删除节点右边的最左节点或左边的最右节点的数据替换为待删除数据,再将用于替换的节点删除 这两个节点有个特点:一定比待删除节点的左子节点大并且比其右子节点小,替换后不会影响树的结构特例:将待删除节点右边的最左节点或左边的最右节点为空指针,如图1节点8此外,我们还要考虑待删除节点的父节点与待删除节点的关系(父节点在待删除节点的左边还是右边)
bool erease(const K& key) { node* prve = nullptr; node* cur = _root; while (cur) { if (cur->_key > key) { prve = cur; cur = cur->_left; } else if (cur->_key < key) { prve = cur; cur = cur->_right; } else { if (cur->_right == nullptr) { //单叉树 if (prve == nullptr) { _root = cur->_left; } //正常情况,无双子,右边为空 else { if (prve->_left == cur) { prve->_left = cur->_left; } else if (prve->_right == cur) { prve->_right = cur->_left; } } delete cur; return true; } else if (cur->_left == nullptr) { //单叉树 if (prve == nullptr) { _root = cur->_right; } //正常情况,无双子,左边为空 else { if (prve->_left == cur) { prve->_left = cur->_right; } else if (prve->_right == cur) { prve->_right = cur->_right; } } delete cur; return true; } //双子,替换法,左右都不为空 else { node* RPlaceParent = cur; node* RPlace = cur->_right; while (RPlace->_left)//出循环后左边指向空 { RPlaceParent = RPlace; RPlace = RPlace->_left; } cur->_key = RPlace->_key; //赋值到右边 if (RPlaceParent->_left == RPlace) { RPlaceParent->_left = RPlace->_right; } else if (RPlaceParent->_right == RPlace) { RPlaceParent->_right = RPlace->_right; } delete RPlace; return true; } } }return false; }
我相信,许多人在使用替换法时回想将RPlaceParent=nullptr,我最开始也是,此时会有什么问题? 以我的图1为例,假如删除根节点8此时发生什么? 答案:最终RPlaceParent=nullptr,可实际上RPlaceParent=10,因此,我们将RPlaceParent=cur,防止出现这种情况 }

自此,二叉树代码实现完成,但这篇博客还未结束,最关键的复盘没有,就违背了我写这篇博客的初衷

4.错误复盘

二叉树实现过程,我的错误有

a.将RPlaceParent赋值为空

b.delete了prve,RPlaceParent父节点,这会导致逻辑混乱

c.将用于试探的值cur,RPlace当成了要删除的值,实际应该用它们的父节点比较

这三个错误,其实都能归因为c,由此次经验,我明白了试探法的核心

方法论

将抽象问题具体化->将具体的问题拆分为多个简单问题->逐个击破->复盘

由此,我的第一篇博客完成

http://www.cnnetsun.cn/news/2109153.html

相关文章:

  • Xinference-v1.17.1与Latex集成:AI辅助的学术论文写作系统
  • RISC-V向量处理器AX45MPV架构解析与应用
  • AI虫子种类识别数据集分享(适用于YOLO系列深度学习分类检测任务)
  • Playwright CLI × Claude Code 自动化测试实战
  • Tree-GRPO:用可微决策树实现可解释强化学习策略优化
  • 注意力机制原理与NumPy实现详解
  • AgenticHub:macOS原生AI工具资源管理器,高效管理MCP服务器与Agent技能
  • Clang在Dev-C++中如何静态链接标准库
  • 构建个人神经科学知识库:基于Git与Markdown的“第二大脑”实践
  • Notte框架:混合智能体模式实现低成本高可靠的Web自动化
  • LT3507三路降压稳压器设计与优化指南
  • Java的java.lang.foreign优化模式
  • 从STP到RSTP:一次协议升级,如何让华为交换机网络收敛时间从50秒降到1秒内?
  • 烧结含油轴承简介
  • 面试官最爱问的AMBA AHB协议细节:从Burst传输到Split响应,一次讲透避坑点
  • 上市公司产学研合作及专利数据(1998-2022年)
  • ACE-Step音乐生成模型:零基础5分钟创作多语言歌曲,小白也能当音乐人
  • 地级市-数字经济政策词频数据(1986-2023年)
  • PyTorch模型部署实战:如何用load_state_dict优雅地加载预训练权重到自定义网络?
  • STM32调试神器USMART避坑指南:从HAL库移植到函数指针传参的实战详解
  • AI Agent工具调用实战:Composio标准化集成与自动化开发指南
  • MCP插件兼容性崩塌预警,2026 Q1已致47%企业开发流中断,如何紧急迁移并重构?
  • Qwen3.5-9B-AWQ-4bit Qt桌面应用开发:跨平台AI助手客户端
  • 如何用Preact构建高性能社交互动界面:完整开发指南
  • OpenCV与SVM结合:高效图像分类实战指南
  • 终极指南:理解KMP字符串匹配算法的原理与实现
  • Phi-3-mini-4k-instruct-gguf入门必读:GGUF格式原理、vLLM加速机制与Chainlit架构
  • 枯木想要逢春: 我们不能因为过去的伤害而心死
  • 工业级网络视频录像机(NVR)日志分析:千问3.5-9B智能运维案例
  • 如何将WebAssembly集成到Preact应用:提升性能的完整指南