二叉树进阶 --- 中

目录

1. find 的递归实现

2. insert 的递归实现

3. erase 的递归实现

3.1. 被删除的节点右孩子为空

3.2. 被删除的节点左孩子为空

3.3. 被删除的节点左右孩子都不为空

4. 析构函数的实现

5. copy constructor的实现

6. 赋值运算符重载

7. 搜索二叉树的完整实现


1. find 的递归实现

find的递归实现较为简单,思路是:根据当前节点的 key 与传入的 key 作比较:

  • 如果前者大于后者,那么当前节点往左子树走;
  • 如果前者小于后者,那么当前节点往右子树走;
  • 如果两者相等,返回true;
  • 走到空,返回false。

代码实现:

// 对外提供的
bool find_recursion(const T& key)
{return _find_recursion(_root, key);
}
// 类中私有的
bool _find_recursion(Node* root, const T& key)
{if (root == nullptr)return false;else{if (root->_key < key)return _find_recursion(root->_right, key);else if (root->_key > key)return _find_recursion(root->_left, key);elsereturn true;}
}

2. insert 的递归实现

insert分为两个过程

  • 第一个过程:找到合适位置;
  • 第二个过程:构建新节点,并完成连接关系。

假如现在我们已经得到了合适的插入位置,那么如何连接呢?

例如,如下图所示:我们要插入13这个数据,现在的关键问题是,如何将15和13这两个节点连接起来呢? 具体如下:

第一种方法:调用函数时,将父亲节点即这里的15也传进来。找到合适位置,创建节点并连接。

但是我们在这里提出一个较好玩的玩法,利用引用传参,如下所示:

// 对外提供的
bool insert_recursion(const T& key)
{    return _insert_recursion(_root, key);
}
// 类中私有的
bool _insert_recursion(Node*& root, const T& key)
{if (root == nullptr){// 走到空, 说明找到了目标位置, 需要构建新节点, 并完成连接关系// 在这里, 我们用上图解释:// root就是15这个节点的左孩子的引用,即root就是15的左孩子// 给root new了一个node(key),等价于插入了这个节点,并连接了起来.root = new Node(key);return true;}else{if (root->_key < key)return _insert_recursion(root->_right, key);else if (root->_key > key)return _insert_recursion(root->_left, key);elsereturn false;}
}

3. erase 的递归实现

对于erase的递归实现,其实也可以分为两个过程:

  • 第一个过程:找到这个要删除的特殊节点;
  • 第二个过程:可以分为三种情况(左孩子为空、右孩子为空、左右孩子都不为空),根据不同情况进行删除。

假设我们现在已经得到了要删除节点的位置,该如何删除呢?

3.1. 被删除的节点右孩子为空

如图所示:我们要删除6号节点(其右孩子为空),该如何删除:

 

由于 root 是4的右孩子的引用,且 root 的右孩子为空,那么root = root->_left,就可以将4的右孩子由6变更为5,我们在删除6即可,因此我们需要提前保存6节点,当指向变更之后,delete 6。

3.2. 被删除的节点左孩子为空

如图所示:我们要删除15号节点(其左孩子为空),该如何删除:

由于 root 是8的右孩子的引用,且 root 没有左孩子,那么我们此时只需要更改 root 即可,让 root 到它的右孩子 (root = root->_right),等价于将8连接了19,当然我们也需要提前将 root 节点进行保存,更改指向后,在释放 root 节点即可。

3.3. 被删除的节点左右孩子都不为空

较为复杂的就是第三种情况了,由于此时被删除节点有两个孩子,因此无法像上面两种情况进行处理。此时我们还是要利用循环实现的思路:

  • (1):从被删除的节点开始,先找到左子树的最大节点or右子树的最小节点(我在这里称之为"合适节点");
  • (2):交换这个"合适结点"和被删除节点的key;
  • (3):将删除原节点转化为删除我们后找的这个"合适节点"。

在这里我们用实例说明,如下图所示:如果我要删除下图中的4,该如何删除?

我在这里实现的"合适节点"是: 左子树的最大(右)节点

相信前两个过程是没有困难的,最后一步可能不好实现,但是当我们经过了前两个过程,我们发现被删除节点变成了我们找到的"合适节点",而且这个"合适节点"很有特征,如果它是左子树的最大值,那么它一定不会有右子树,反之,如果他是右子树的最小节点,那么它一定不会有左子树。因此我们可以在递归一次,如果"合适节点"是左子树的最大节点,那么我们递归树的左子树即可,反之如果是右子树的最小节点,那么我们递归树的右子树即可。

代码如下:

// 对外提供的
bool erase_recursion(const T& key)
{return _erase_recursion(_root, key);
}
// 类中私有的
bool _erase_recursion(Node*& root, const T& key)
{if (!root)return false;else{// 如果当前节点的key > 目标key,那么递归它的左子树即可if (root->_key > key)return _erase_recursion(root->_left, key);// 如果当前节点的key < 目标key,那么递归它的右子树即可else if (root->_key < key)return _erase_recursion(root->_right, key);// 如果找到了,进行删除else{// 此时的root就是要删除的节点Node* del = root;// a. 左子树为空if (root->_left == nullptr)root = root->_right;// b. 右子树为空else if (root->_right == nullptr)root = root->_left;// c. 左右子树都不为空else{// 左子树的最右节点Node* left_max = root->_left;while (left_max->_right)left_max = left_max->_right;// 交换"合适节点"和"被删除节点"的keystd::swap(left_max->_key, root->_key);// 在这里递归左子树即可return _erase_recursion(root->_left, key);}delete del;del = nullptr;return true;}}
}

4. 析构函数的实现

析构函数的实现我们依据的是后序的思想(LRN),先析构左子树、然后是右子树、最后才是根。这种实现的原因是是少了许多的记录信息,例如在这里我们就不用记录下一个节点。因为我们释放的就是当前的叶子节点。

具体实现如下:

~BinarySearchTree()
{_BSTDestroy(_root);
}
// 注意我们这里传递的是根的引用
void _BSTDestroy(Node*& root)
{if (root == nullptr)return;else{// 依据后序的思想_BSTDestroy(root->_left);_BSTDestroy(root->_right);delete root;root = nullptr;}
}

5. copy constructor的实现

老生常谈的问题,如果我们没有显示实现拷贝构造函数,那么编译器默认生成的拷贝构造会对内置类型按照字节序的方式进行拷贝,对自定义类型成员属性会去调用它的拷贝构造函数。而字节序的方式进行拷贝会带来两个问题:

  • 其一,其中一个对象的修改会影响另一个对象;
  • 其二,同一空间会被析构两次,进程crash。

因此,我们在这里必须要实现深拷贝,那如何实现呢?我们可以借助前序的思想(NLR)。从根节点开始进行构造节点,然后递归构造它的左子树和右子树。注意构造的时候需要它们的连接关系。

代码如下:

BinarySearchTree(const BinarySearchTree<T>& copy)
{_root = _creat_new_root(copy._root);
}
Node* _creat_new_root(Node* root)
{// 如果遇到空了,就不用构造了if (root == nullptr)return nullptr;else{// 根据前序的思想(NLR),依次构造它的根、左子树、右子树   // 同时将它们连接起来Node* new_root = new Node(root->_key);new_root->_left = _creat_new_root(root->_left);new_root->_right = _creat_new_root(root->_right);return new_root;}
}

6. 赋值运算符重载

赋值运算符重载就比较简单了,因为我们已经实现了copy constructor,在这里利用传值传参会进行拷贝构造的特性实现我们的赋值

代码如下:

// 传值传参会进行拷贝构造
BinarySearchTree<T>& operator=(BinarySearchTree<T> copy)
{std::swap(_root, copy._root);return *this;
}

7. 搜索二叉树的完整实现

代码如下:

#ifndef _BINARY_SEARCH_TREE_HPP_
#define _BINARY_SEARCH_TREE_HPP_#include <iostream>namespace Xq
{template<class T>struct BinarySearchTreeNode{BinarySearchTreeNode<T>* _left;BinarySearchTreeNode<T>* _right;T _key;BinarySearchTreeNode(const T& key) :_key(key), _left(nullptr), _right(nullptr) {}};template<class T>class BinarySearchTree{private:typedef BinarySearchTreeNode<T> Node;public:BinarySearchTree(Node* root = nullptr) :_root(root) {}bool insert(const T& key){// 1. 如果是空树,直接对_root赋值即可,插入成功并返回trueif (_root == nullptr){_root = new Node(key);return true;}else{// step 1: 先找目标位置Node* cur = _root;// 为了更好的连接新节点, 因此记录父节点Node* parent = nullptr;while (cur){// 如果当前节点的Key大于目标Key// 当前节点应该向左子树走if (cur->_key > key){parent = cur;cur = cur->_left;}// 如果当前节点的Key小于目标Key// 当前节点应该向右子树走else if (cur->_key < key){parent = cur;cur = cur->_right;}else{// 找到了相同的 key, 在这里不插入return false;}}// cur 走到了空, 即 cur 就是合适的位置cur = new Node(key);// 我们需要判断cur是parent的左节点还是右节点// 如果key小于parent的key,那么插入左节点if (key < parent->_key)parent->_left = cur;// 反之连接到右节点elseparent->_right = cur;return true;}}bool find(const T& key){// 1. 从根节点开始Node* cur = _root;while (cur){// 2. 如果当前关键字大于目标关键字,那么向左子树走if (cur->_key > key)cur = cur->_left;// 3. 如果小于目标关键字,那么向右子树走else if (cur->_key < key)cur = cur->_right;// 4. 相等,就返回trueelsereturn true;}// 5. 循环结束,说明没找到, 返回falsereturn false;}bool erase(const T& key){// 先找要删除的节点Node* del = _root;Node* del_parent = nullptr;while (del){if (del->_key < key){del_parent = del;del = del->_right;}else if (del->_key > key){del_parent = del;del = del->_left;}else{// 锁定了要删除的节点// 分三种情况:// case 1: 左子树为空if (del->_left == nullptr){// 如果要删除的节点是根if (del == _root){Node* newroot = del->_right;delete _root;_root = newroot;}else{// 托孤法删除if (del_parent->_left == del)del_parent->_left = del->_right;elsedel_parent->_right = del->_right;delete del;del = nullptr;}}// case 2: 右子树为空else if (del->_right == nullptr){if (_root == del){Node* newroot = del->_left;delete _root;_root = newroot;}else{if (del_parent->_left == del)del_parent->_left = del->_left;elsedel_parent->_right = del->_left;delete del;del = nullptr;}}// case 3: 左右子树都不为空else{// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点if (del->_right)_erase_right_min_node(del);else_erase_left_max_node(del);}return true;}}return false;}bool find_recursion(const T& key){return _find_recursion(_root, key);}bool insert_recursion(const T& key){return _insert_recursion(_root, key);}bool erase_recursion(const T& key){return _erase_recursion(_root, key);}~BinarySearchTree(){_BSTDestroy(_root);}BinarySearchTree(const BinarySearchTree<T>& copy){_root = _creat_new_root(copy._root);}// 传值传参会进行拷贝构造BinarySearchTree<T>& operator=(BinarySearchTree<T> copy){std::swap(_root, copy._root);return *this;}void InOrder(){_InOrder(_root);std::cout << std::endl;}private:void _InOrder(Node* root){if (root){_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);}}bool _find_recursion(Node* root, const T& key){if (root == nullptr)return false;else{if (root->_key < key)return _find_recursion(root->_right, key);else if (root->_key > key)_find_recursion(root->_left, key);elsereturn true;}}bool _insert_recursion(Node*& root, const T& key){if (root == nullptr){root = new Node(key);return true;}else{if (root->_key < key)return _insert_recursion(root->_right, key);else if (root->_key > key)return _insert_recursion(root->_left, key);elsereturn false;}}void _erase_right_min_node(Node* del){// 从被删除结点开始, 找右子树的最小(左)节点Node* right_min = del->_right;// 并记录这个节点的父亲节点, 让其从del开始Node* right_min_parent = del;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}// 交换这个节点和要删除节点的 keystd::swap(del->_key, right_min->_key);// 将删除 del 转化为删除 right_min (托孤法删除)if (right_min_parent->_left == right_min)right_min_parent->_left = right_min->_right;elseright_min_parent->_right = right_min->_right;delete right_min;right_min = nullptr;}void _erase_left_max_node(Node* del){// 从被删除节点开始, 找左子树的最大(右)节点Node* left_max = del->_left;// 并记录这个节点的父亲节点, 让其从del开始Node* left_max_parent = del;while (left_max->_right){left_max_parent = left_max;left_max = left_max->_right;}// 交换这个节点和要删除节点的 keystd::swap(del->_key, left_max->_key);// 将删除 del 转化为删除 left_max (托孤法删除)if (left_max_parent->_left == left_max)left_max_parent->_left = left_max->_left;elseleft_max_parent->_right = left_max->_left;delete left_max;left_max = nullptr;}bool _erase_recursion(Node*& root, const T& key){if (!root)return false;else{// 如果当前节点的key > 目标key,那么递归它的左子树即可if (root->_key > key)return _erase_recursion(root->_left, key);// 如果当前节点的key < 目标key,那么递归它的右子树即可else if (root->_key < key)return _erase_recursion(root->_right, key);// 如果找到了,进行删除else{// 此时的root就是要删除的节点Node* del = root;// a. 左子树为空if (root->_left == nullptr)root = root->_right;// b. 右子树为空else if (root->_right == nullptr)root = root->_left;// c. 左右子树都不为空else{// 左子树的最右节点Node* left_max = root->_left;while (left_max->_right)left_max = left_max->_right;// 交换"合适节点"和"被删除节点"的keystd::swap(left_max->_key, root->_key);// 在这里递归左子树即可return _erase_recursion(root->_left, key);}delete del;del = nullptr;return true;}}}Node* _creat_new_root(Node* root){// 如果遇到空了,就不用构造了if (root == nullptr)return nullptr;else{// 根据前序的思想(NLR),依次构造它的根、左子树、右子树   // 同时将它们连接起来Node* new_root = new Node(root->_key);new_root->_left = _creat_new_root(root->_left);new_root->_right = _creat_new_root(root->_right);return new_root;}}// 注意我们这里传递的是根的引用void _BSTDestroy(Node*& root){if (root == nullptr)return;else{// 依据后序的思想_BSTDestroy(root->_left);_BSTDestroy(root->_right);delete root;root = nullptr;}}private:Node* _root;};
}#endif

二叉树进阶 --- 中,结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/10350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树的基本介绍

引入 定义 表示 相关概念 结点&#xff1a;数据元素与指向分支的指针两部分组成 树的深度&#xff1a;树中结点的最大层次 将树A结点(根结点)去掉&#xff0c;树A就变成了森林 区别 实现

再谈毕业论文设计投机取巧之IVR自动语音服务系统设计(信息与通信工程A+其实不难)

目录 举个IVR例子格局打开&#xff0c;万物皆能IVR IVR系统其实可盐可甜。还能可圈可点。 戎马一生&#xff0c;归来依然IVR。 举个IVR例子 以下是IVR系统的一个例子。 当您拨打电话进入IVR系统。 首先检验是否为工作时间。 如是&#xff0c;您将被送入ivr-lang阶段&#xff0…

管道液位传感器可以应用在哪些领域

管道液位传感器是一种利用光学原理来检测水管液位的传感器&#xff0c;其工作原理基于光线在水和空气中折射率不同的特性。通过光电管道传感器&#xff0c;可以有效解决传统机械式传感器存在的低精度、卡死失效等问题&#xff0c;同时也避免了电容式传感器因感度衰减而导致的不…

【Linux笔记】 基础指令(二)

风住尘香花已尽 日晚倦梳头 重命名、剪切指令 -- mv 简介&#xff1a; mv 命令是 move 的缩写&#xff0c;可以用来移动文件或者将文件改名&#xff0c;是 Linux 系统下常用的命令&#xff0c;经常用来备份文件或者目录 语法&#xff1a; mv [选项] 源文件或目录 目标文件或目录…

泵站远程启停

随着物联网技术的迅猛发展&#xff0c;传统泵站的管理方式正面临前所未有的变革。在这一变革的浪潮中&#xff0c;HiWoo Cloud平台凭借其卓越的技术实力和创新理念&#xff0c;为泵站远程启停控制带来了全新的解决方案。本文将详细介绍HiWoo Cloud平台在泵站远程启停方面的应用…

红魔6/6pro9008救砖刷机详细教程

1. 安装驱动和打开工具&#xff1a; - 首先安装高通Qualcomm驱动。 - 安装完成后&#xff0c;以管理员权限打开Qualcomm_tool高通工具。 2. 选择引导文件&#xff1a; - 选择引导刷机包内的ELF引导文件&#xff0c;并点击打开。 3. 配置rawprogram XML文件&#xff1a; - 点击r…

vivado Spartan-7 配置存储器器件

下表所示闪存器件支持通过 Vivado 软件对 Spartan -7 器件执行擦除、空白检查、编程和验证等配置操作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列非易失性存储器 进行擦除、空白检查、编程和验证。赛灵…

礼赞劳动节,致敬劳动者。节日随想:疾笔耕耘也是一种劳动方式。

马克思也快诞辰了206年了&#xff0c;恩格斯领导的第二国际通过的决议节日也迎来了134岁的生日了&#xff0c;我也继续在劳动的路上。 五月是值得纪念的日子&#xff0c;作为一名无上光荣的分子&#xff0c;无比仰慕崇拜的两位先驱前辈大胡子&#xff0c;其一 生于斯&#xff0…

VTK官方示例

VTK官方示例 -vtk字體 #!/usr/bin/env python# noinspection PyUnresolvedReferences import vtkmodules.vtkInteractionStyle # noinspection PyUnresolvedReferences import vtkmodules.vtkRenderingFreeType # noinspection PyUnresolvedReferences import vtkmodules.vtk…

标准参编征集|《第三方运维服务水平评价指南 工业废水处理设施》

目前&#xff0c;对于工业废水处理设施第三方运维服务的标准&#xff0c;国家和行业未曾出台有针对性的评价标准和规范&#xff0c;工业企业和工业园区对第三方运维服务的监督、考核、评价体系需要进一步补充和完善。 本标准的编制旨在帮助第三方运营单位从运营技术和管理举措…

PDF文件恢复:四种实用方法全解析

如何恢复已删除的PDF文件&#xff1f; PDF是Portable Document Format&#xff08;便携式文档格式&#xff09;的缩写&#xff0c;是一种由Adobe Systems开发的文件格式。PDF文件可以包含文本、图形、链接、多媒体以及其他各种元素&#xff0c;并且能够在各种操作系统和设备上…

[数据集][目标检测]纸箱子检测数据集VOC+YOLO格式8375张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8375 标注数量(xml文件个数)&#xff1a;8375 标注数量(txt文件个数)&#xff1a;8375 标注…

车辆充电桩|基于Springboot+vue的车辆充电桩管理系统的设计与实现(源码+数据库+文档)

车辆充电桩管理系统 目录 基于Springboot&#xff0b;vue的车辆充电桩管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1 前台功能模块 4.1.1 首页功能 4.1.2 用户后台管理 2 后台功能模块 4.2.1 管理员功能 4.2.2 维修员功能 四、数据库设计 五、核…

解决“电脑开机黑屏Explorer进程卡死“问题

今天&#xff0c;给台式机按电源键&#xff0c;进入windows系统时&#xff0c;发现电脑黑屏了&#xff0c;昨天还好好的&#xff0c;怎么今天电脑桌面进不去了&#xff1f;想起Windows XP、Windows 7、Windows 10 、Windows 11等系统&#xff0c;在使用多个文件拷贝时&#xff…

AI写作推荐-写文ai-AI在线写作生成器-3步完成写作任务

AI写作利器&#xff1a;推荐几款神助攻文案创作工具 随着技术的进步&#xff0c;人工智能&#xff08;AI&#xff09;已达到高级水平&#xff0c;在众多领域展现其强大能力。 在文本创作的领域&#xff0c;人工智能&#xff08;AI&#xff09;应用已显著地提升了写作效率和创意…

2024数维杯数学建模B题生物质和煤共热解问题的研究原创论文分享

大家好&#xff0c;从昨天肝到现在&#xff0c;终于完成了2024数维杯数学建模挑战赛B题的完整论文啦。 实在精力有限&#xff0c;具体的讲解大家可以去讲解视频&#xff1a; 2024数维杯数学建模B题煤共热解每一问高质量完整代码讲解&#xff01;_哔哩哔哩_bilibili 2024数维杯…

学习Java的日子 Day44 初识前端

Day44 HTML 学习路线&#xff1a; 前端&#xff1a;展示页面、与用户交互 — HTML 后端&#xff1a;数据的交互和传递 — JavaEE/JavaWeb 1.B/S和C/S B/S&#xff1a;浏览器/服务器 教务系统 C/S&#xff1a;客户端/服务器 优缺点 1.开发/维护成本&#xff1a;B/S相对低 2.运算…

IPv6资产测绘哪家强?揭秘新一代网络空间资产测绘平台的独门秘籍

网络空间资产测绘&#xff0c;即通过一系列技术手段&#xff0c;对网络中的各类资产进行全面的发现、分类和定位&#xff0c;为各类用户提供精准的数据支撑和决策依据。网络空间资产测绘作为一门新兴的交叉学科&#xff0c;融合了计算机网络技术、数据挖掘、人工智能、信息安全…

解构复合人工智能系统(Compound AI Systems):关键术语、理论、思路、实践经验

编者按&#xff1a; 大模型的出现为构建更智能、更复杂的人工智能系统带来了新的契机。然而&#xff0c;单一的大模型难以应对现实世界中错综复杂的问题&#xff0c;需要与其他模块相结合&#xff0c;构建出复合人工智能系统&#xff08;Compound AI Systems&#xff09;。 本文…

Python经典案例爬取豆瓣Top250电影数据

随着网络数据的日益丰富&#xff0c;如何从海量的信息中快速、准确地提取出有价值的数据&#xff0c;成为了许多开发者和技术爱好者关注的焦点。在这个过程中&#xff0c;网络爬虫技术凭借其强大的数据获取能力&#xff0c;成为了数据分析和挖掘的重要工具。本文将通过一个经典…