定义

树也是基于节点的数据结构,和链表不同的是,树的节点可以指向多个节点。首先对树的一些常用术语进行说明:

  • 最上面的节点叫做根节点,根位于树顶,如图中的节点A;
  • 和族谱一样,节点有后代和祖先;节点的后代即起源于该节点的全部节点,祖先即可以派生出该节点的节点;
  • 树有层级,每一层都是树的一行,如上图,树有三层;
  • 树的一个属性是它是否平衡,即如果子树中的节点数量相同,则这棵树是平衡的,上图中的树就是不平衡的;

二叉查找树

二叉查找树也是树的一种,其特征如下:

  • 每个节点最多有一个左子节点和右子节点
  • 一个节点的左子树中的值都小于节点本身,同时右子树中的值都大于节点本身;

实现

二叉树实现如下:

#include <iostream>
#include <memory>// 二叉树节点类
template <typename T>
class TreeNode {
public:T data;std::shared_ptr<TreeNode<T>> left;std::shared_ptr<TreeNode<T>> right;TreeNode(T value) : data(value), left(nullptr), right(nullptr) {}
};

二叉树查找

二叉树查找步骤如下:

  1. 以根节点为当前节点;
  2. 读取当前节点数值;
  3. 如果当前数值为要查找的数值,则停止查找并返回;
  4. 如果当前值大于要查找的数值,则在其左子树中继续查找;
  5. 如果当前值小于要查找的数值,则在右子树中继续查找;
  6. 重复1-5步,直到找到对应数值,或者到达了树的底端,即数值不在该树中;

注意观察可以发现,每次查找都会排除大约一半的节点,因此可以推测二叉查找树的查找效率为O(log_{2}N),之所以说大约为这个效率,是因为只有二叉查找树的每一行节点都填满(即是一棵平衡二叉树),则上述特性才成立即:如果一棵平衡的二叉树中有N个节点,则就会有大约logN层。代码实现如下:

bool searchRecursive(std::shared_ptr<TreeNode<T>> node, T value) const {if (!node) {return false;}if (value == node->data) {return true;} else if (value < node->data) {return searchRecursive(node->left, value);} else {return searchRecursive(node->right, value);}}

二叉树插入

二叉树的插入是基于二叉树的查找,即找到要插入的位置后插入(方法同上述中的二叉树查找),其效率为O(logN)+1,代码实现如下:

    // 递归插入节点std::shared_ptr<TreeNode<T>> insertRecursive(std::shared_ptr<TreeNode<T>> node, T value) {if (!node) {return std::make_shared<TreeNode<T>>(value);}if (value < node->data) {node->left = insertRecursive(node->left, value);} else if (value > node->data) {node->right = insertRecursive(node->right, value);}return node;}

二叉树删除

二叉树查找树的删除是最复杂的部分,其步骤如下:

  • 如果要删除的节点没有子节点,那么就可以直接删除该节点;
  • 如果要删除的节点有一个子节点,那么就在删除该节点的同时把子节点插到该节点的位置;
  • 如果要删除的节点有两个子节点,则需要把要删除的节点替换为其后继节点,后继节点即大于被删除节点的所有子节点中最小的那个;
  • 如果需要寻找后继节点,则需要先移动到被删除节点的右子节点,然后沿左节点的链接移动到左子结点,直到找不到任何左子结点为止,最下的这个值即后继节点;
  • 如果后继节点有一个右子节点,则在将后继节点放置到被删除节点的位置之后,把这个右子节点变成后继节点曾经的父节点的左子结点。

删除的效率和插入以及查找类似,均为O(logN),代码如下:

    // 查找最小节点std::shared_ptr<TreeNode<T>> findMin(std::shared_ptr<TreeNode<T>> node) const {while (node && node->left) {node = node->left;}return node;}// 递归删除节点std::shared_ptr<TreeNode<T>> deleteRecursive(std::shared_ptr<TreeNode<T>> node, T value) {if (!node) {return nullptr;}if (value < node->data) {node->left = deleteRecursive(node->left, value);} else if (value > node->data) {node->right = deleteRecursive(node->right, value);} else {// 找到要删除的节点if (!node->left) {return node->right;} else if (!node->right) {return node->left;}// 有两个子节点的情况:找到右子树的最小节点替换当前节点std::shared_ptr<TreeNode<T>> temp = findMin(node->right);node->data = temp->data;node->right = deleteRecursive(node->right, temp->data);}return node;}

遍历-前、中、后

二叉查询树的遍历,即访问所有节点,分类为三种:前序、中序、后序。其实这里的前、中、后针对的是调用遍历函数时的当前节点,前的意思就是先访问当前节点,再访问左、右子节点;中的意思就是先访问左子节点,再访问当前节点,最后访问右子节点;后的意思是先访问左、右子节点,最后访问本节点,其实现如下:

    // 中序遍历(递归)void inorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;inorderRecursive(node->left);std::cout << node->data << " ";inorderRecursive(node->right);}// 前序遍历(递归)void preorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;std::cout << node->data << " ";preorderRecursive(node->left);preorderRecursive(node->right);}// 后序遍历(递归)void postorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;postorderRecursive(node->left);postorderRecursive(node->right);std::cout << node->data << " ";}

以上就是本节关于二叉查找树的全部内容,树还有很多形式,这里挑选比较常用、简单的树形结构来进行讲解,不过今后有了AI,这些数据结构和算法的实现将会比较简单,程序员更多地是理解原理以及优化。全部代码整理如下:

#include <iostream>
#include <memory>// 二叉树节点类
template <typename T>
class TreeNode {
public:T data;std::shared_ptr<TreeNode<T>> left;std::shared_ptr<TreeNode<T>> right;TreeNode(T value) : data(value), left(nullptr), right(nullptr) {}
};// 二叉查找树类
template <typename T>
class BinarySearchTree {
private:std::shared_ptr<TreeNode<T>> root;// 递归插入节点std::shared_ptr<TreeNode<T>> insertRecursive(std::shared_ptr<TreeNode<T>> node, T value) {if (!node) {return std::make_shared<TreeNode<T>>(value);}if (value < node->data) {node->left = insertRecursive(node->left, value);} else if (value > node->data) {node->right = insertRecursive(node->right, value);}return node;}// 递归查找节点bool searchRecursive(std::shared_ptr<TreeNode<T>> node, T value) const {if (!node) {return false;}if (value == node->data) {return true;} else if (value < node->data) {return searchRecursive(node->left, value);} else {return searchRecursive(node->right, value);}}// 查找最小节点std::shared_ptr<TreeNode<T>> findMin(std::shared_ptr<TreeNode<T>> node) const {while (node && node->left) {node = node->left;}return node;}// 递归删除节点std::shared_ptr<TreeNode<T>> deleteRecursive(std::shared_ptr<TreeNode<T>> node, T value) {if (!node) {return nullptr;}if (value < node->data) {node->left = deleteRecursive(node->left, value);} else if (value > node->data) {node->right = deleteRecursive(node->right, value);} else {// 找到要删除的节点if (!node->left) {return node->right;} else if (!node->right) {return node->left;}// 有两个子节点的情况:找到右子树的最小节点替换当前节点std::shared_ptr<TreeNode<T>> temp = findMin(node->right);node->data = temp->data;node->right = deleteRecursive(node->right, temp->data);}return node;}// 中序遍历(递归)void inorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;inorderRecursive(node->left);std::cout << node->data << " ";inorderRecursive(node->right);}// 前序遍历(递归)void preorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;std::cout << node->data << " ";preorderRecursive(node->left);preorderRecursive(node->right);}// 后序遍历(递归)void postorderRecursive(std::shared_ptr<TreeNode<T>> node) const {if (!node) return;postorderRecursive(node->left);postorderRecursive(node->right);std::cout << node->data << " ";}public:BinarySearchTree() : root(nullptr) {}// 插入值void insert(T value) {root = insertRecursive(root, value);}// 查找值bool search(T value) const {return searchRecursive(root, value);}// 删除值void remove(T value) {root = deleteRecursive(root, value);}// 中序遍历void inorder() const {std::cout << "中序遍历: ";inorderRecursive(root);std::cout << std::endl;}// 前序遍历void preorder() const {std::cout << "前序遍历: ";preorderRecursive(root);std::cout << std::endl;}// 后序遍历void postorder() const {std::cout << "后序遍历: ";postorderRecursive(root);std::cout << std::endl;}// 判断树是否为空bool isEmpty() const {return root == nullptr;}
};// 示例用法
int main() {BinarySearchTree<int> bst;// 插入一些值bst.insert(50);bst.insert(30);bst.insert(20);bst.insert(40);bst.insert(70);bst.insert(60);bst.insert(80);// 遍历二叉树bst.inorder();bst.preorder();bst.postorder();// 搜索值std::cout << "搜索 40: " << (bst.search(40) ? "找到" : "未找到") << std::endl;std::cout << "搜索 90: " << (bst.search(90) ? "找到" : "未找到") << std::endl;// 删除值std::cout << "\n删除 20\n";bst.remove(20);bst.inorder();std::cout << "删除 30\n";bst.remove(30);bst.inorder();std::cout << "删除 50\n";bst.remove(50);bst.inorder();return 0;
}

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

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

相关文章

JVM-默背版

1.JVM对sychronized的优化&#xff1a;锁膨胀、锁消除、锁粗化、自适应自旋锁 &#xff08;1&#xff09;锁膨胀&#xff1a;从无锁、偏向锁、轻量级锁、重量级锁的过程叫做锁膨胀。在JDK1.6以前&#xff0c;sychronized是由重量级锁实现的&#xff0c;加锁和解锁的过程需要从用…

Mac M 系列芯片 YOLOv8 部署教程(CPU/Metal 后端一键安装)

在 Mac M 系列芯片&#xff08;Apple Silicon/ARM 架构&#xff09;上部署 YOLOv8&#xff0c;有一些注意事项&#xff1a;PyTorch 需要安装 ARM 原生版本&#xff0c;推理可利用 Metal 后端加速 CPU。本文教你一步步完成环境配置、模型下载、依赖安装和验证推理。1️⃣ 环境准…

Python爬虫实战:研究Units模块,构建气象数据采集和分析系统

1. 引言 1.1 研究背景 随着信息技术的飞速发展,互联网已成为全球最大的信息库,涵盖气象、金融、医疗、农业等多个领域的海量数据。这些数据蕴含着巨大的潜在价值,如何有效获取并深入分析这些数据成为当下研究的热点。Python 作为一种功能强大的编程语言,凭借其丰富的库资…

网页设计模板 HTML源码网站模板下载

互联网已成为现代社会不可或缺的一部分&#xff0c;网站则是连接线上与线下世界的桥梁。无论是用于展示个人作品集、推广商业产品还是提供公共服务信息&#xff0c;一个设计精良且功能完善的网站都能发挥巨大作用。然而&#xff0c;传统的手工编码方式不仅耗时费力&#xff0c;…

Flink KeyedProcessFunction为什么能为每个key定义State和Timer?

问题描述 一个常见的开窗逻辑&#xff08;12H 或者 500条&#xff09;&#xff1a; import org.apache.flink.api.common.state.ValueState; import org.apache.flink.api.common.state.ValueStateDescriptor; import org.apache.flink.api.common.typeinfo.Types; import or…

【C++】模版初阶---函数模版、类模版

&#x1f31f;个人主页&#xff1a;第七序章 &#x1f308;专栏系列&#xff1a;C&#xff0b;&#xff0b; 目录 ❄️前言&#xff1a; &#x1f308;1.泛型编程&#xff1a; &#x1f308;2.函数模板 &#x1f36d;2.1函数模板概念 &#x1f36d;2.2函数模板格式 &am…

查找算法(Java)

目录 一.定义 二.分类 三.线性查找 原理&#xff1a; 思路分析 代码实现 例题实践 1.两数之和 方法一&#xff1a;暴力穷举法 思路分析 代码实现 方法二&#xff1a;创建哈希表 思路分析 代码实现 2.移动零 思路分析 代码实现 四.二分查找 原理&#xff1a; …

计算机网络--四层模型,IP地址和MAC地址

四层模型&#xff1a;分别是应用层&#xff0c;传输层&#xff0c;网络层和链路层。应用层&#xff1a;提供了应用程序之间相互通信的接口&#xff0c;允许用户访问网络服务。这一层定义了应用程序如何与底层网络进行交互。例如HTTP协议。传输层&#xff1a;它处理数据的分段、…

解析、创建Excel文件的开源库OpenXLSX介绍

OpenXLSX是一个C库&#xff0c;用于读取、写入、创建和修改.xlsx格式的Microsoft Excel文件&#xff0c;源码地址&#xff1a;https://github.com/troldal/OpenXLSX &#xff0c;License为BSD-3-Clause&#xff0c;可在Windows、Linux、MaCOS平台上使用。最新发布版本为v0.3.2&…

【C++】C++11 篇二

【C】C11 篇二前言移动构造函数移动赋值运算符重载类成员变量初始化 &#xff08;缺省值出自C11强制生成默认函数的关键字default:禁止生成默认函数的关键字delete:继承和多态中的final与override关键字&#xff08;出自C11可变参数模板递归函数方式展开参数包逗号表达式展开参…

构建Python环境的几种工具

本文主要介绍如何构建Python环境来处理不同的工作。 1.常用的构建Python环境的工具 ①venv(内置模块):Python 3.3 内置标准库模块&#xff0c;无需额外安装。 ②virtualenv:venv的前身&#xff0c;功能更强大且支持旧版Python。 ③conda:来自 Anaconda 或 Miniconda。不仅能…

c#项目编译时外部依赖文件的同步问题

很多场景因为资源文件太多或太大无法放到资源里面或者是依赖的dll文件&#xff0c;需要编译时同步到bin\debug或bin\release下的&#xff0c;这里面要修改工程文件代码实现。 比如&#xff0c;我把这个项目依赖的dll和附加文件放到ref_dll文件夹里面&#xff0c;希望编译的时候…

数学建模常用算法-模拟退火算法

一、模拟退火算法模拟退火的灵感来源于物理中的 “退火过程”—— 将金属加热到高温后&#xff0c;缓慢冷却&#xff0c;金属原子会在热能作用下自由运动&#xff0c;逐渐形成能量最低的稳定结构。算法将这一过程抽象为数学模型&#xff1a;“温度 T”&#xff1a;对应物理中的…

架构很简单:业务架构图

缘起业务架构是一个复杂的体系&#xff0c;如何更简单的表达&#xff0c;并能使用起来呢&#xff1f;所谓&#xff1a;大道至简。基于此&#xff0c;这篇文章就开始了。业务是一切架构的开始&#xff0c;如果没有业务&#xff0c;架构又有什么作用呢&#xff1f;所以做架构首先…

【前端埋点】纯前端实现 A/B Test

“纯前端实现 A/B Test”&#xff0c;意思就是 没有后端分流、也不依赖流量网关&#xff0c;那么只能靠前端逻辑来做“流量切分”。 &#x1f3af; 目标 80% 的用户 → A 页面20% 的用户 → B 页面且要保证 同一个用户每次访问结果一致&#xff08;否则用户刷新页面时 A/B 会跳…

Day22_【机器学习—集成学习(3)—Boosting—Adaboost算法】

Adaptive Boosting(自适应提升)是基于 Boosting思想实现的一种集成学习算法&#xff0c;核心思想是通过逐步提高那些被前一步分类错误的样本的权重来训练一个强分类器。一、Adaboost算法直线相当于一个弱学习器&#xff0c;正确的数据权重减小&#xff0c;错误的数据权重增加二…

C#语言入门详解(18)传值、输出、引用、数组、具名、可选参数、扩展方法

C#语言入门详解&#xff08;18&#xff09;传值、输出、引用、数组、具名、可选参数、扩展方法一、传值参数1. 值类型2. 引用类型&#xff0c;并且创建对象3. 引用类型&#xff0c;不创建对象二、引用参数1. 值类型2. 引用类型&#xff0c;创建新对象3. 引用类型&#xff0c;不…

模型部署:(二)安卓端部署Yolov5-v6.0目标检测项目全流程记录

模型部署&#xff1a;&#xff08;二&#xff09;安卓端部署Yolov5-v6.0目标检测项目全流程记录1、下载NCNN项目2、下载基于安卓的yolov5项目3、修改相应的配置文件4、将项目打包成apk包5、部署自己训练的yolov5模型6、相关资料下载&#xff1a;1、下载NCNN项目 # 链接地址 ht…

图形基础算法:如何将点与带曲线边的多边形位置关系算法做稳定

简介判断点与多边形位置关系算法是几何算法中最基础的算法之一&#xff0c;包括布尔运算在内的非常非常多的算法都会用到它。它的稳定是算法库稳定的关键。下面我们从一个边都是直线的多边形开始了解射线法的原理。然后看看引入曲线后会带来哪些问题&#xff0c;以及在实际应用…

react的filber架构

好问题 &#x1f44d; 你问的是 React Fiber 架构。这是 React 16 引入的核心机制&#xff0c;用来解决 React 在大规模更新时的性能问题。下面我给你从 背景 → Fiber 是什么 → 原理 → 优点 → 流程 来系统讲。一、为什么需要 Fiber&#xff1f;在 React 15 及以前&#xff…