C++数据结构与二叉树详解
前言:
在C++编程的世界里,数据结构是构建高效程序的基石,而二叉树则是其中最优雅且应用广泛的数据结构之一。本文将带你深入理解二叉树的本质、实现与应用,助你在算法设计中游刃有余。
一、二叉树的基本概念
1. 什么是二叉树
二叉树是一种特殊的树形结构,其中每个节点最多只能有两个子节点,分别称为左子节点和右子节点。
想象一个家族族谱,每个人最多只能有两个直系后代。从祖先开始,家族成员按照特定规则(如年龄、性别)分布在左右两侧,形成一个有序的家族网络。这就像一棵二叉树,族长是根节点,每代传承形成不同的层级。
struct TreeNode {int val; // 节点存储的值TreeNode* left; // 指向左子节点的指针TreeNode* right; // 指向右子节点的指针// 构造函数TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}};
2. 二叉树的基本特性
- 最多有2^h-1个节点(h为树高)
- 第i层最多有2^(i-1)个节点
- 对于完全二叉树,若有n个节点,则树高h = ⌈log₂(n+1)⌉
这就像一个完美的金字塔结构,每向下一层,可容纳的人数就翻倍。最顶层只有一个法老,第二层可有两个大臣,第三层可有四个将军...层级越低,容纳的人数越多,但权力越小。
3. 二叉树的种类
- 满二叉树:除最后一层外,每层节点都填满,且最后一层所有节点都在最左边
- 完全二叉树:除了最后一层外,其他层的节点数都达到最大,且最后一层的节点都靠左排列
- 平衡二叉树:任意节点的左右子树高度差不超过1
- 二叉搜索树:左子树上所有节点的值都小于根节点,右子树上所有节点的值都大于根节点
想象一个井然有序的图书馆,不同类型的二叉树代表不同的图书排列方式。满二叉树就像是每个书架都完全装满了书;完全二叉树则是除了最后一个书架,其他都满了,且最后书架的书都靠左放置;平衡二叉树则确保每个主题区域的书籍数量大致相当;而二叉搜索树就像按照书名字母顺序排列的书架,使得查找特定书籍变得高效。
二、二叉树的遍历方式
1. 前序遍历(根-左-右)
前序遍历先访问根节点,然后遍历左子树,最后遍历右子树。
想象你是一名探险家,探索一座古老的家族城堡。前序遍历就像你先参观城堡主厅(根节点),然后探索左侧的房间群(左子树),最后才去右侧的花园区域(右子树)。
void preorder(TreeNode* root) {if (root == nullptr) return;cout << root->val << " "; // 访问根节点preorder(root->left); // 遍历左子树preorder(root->right); // 遍历右子树}
2. 中序遍历(左-根-右)
中序遍历先遍历左子树,然后访问根节点,最后遍历右子树。对于二叉搜索树,中序遍历会产生有序序列。
这就像按照时间轴阅读一个家族故事,先了解祖先(左子树)的历史,然后聚焦于当代族长(根节点)的事迹,最后展望家族的未来发展(右子树)。
void inorder(TreeNode* root) {if (root == nullptr) return;inorder(root->left); // 遍历左子树cout << root->val << " "; // 访问根节点inorder(root->right); // 遍历右子树}
3. 后序遍历(左-右-根)
后序遍历先遍历左子树,然后遍历右子树,最后访问根节点。
想象你在拆除一座大楼,必须先拆除左侧的附属建筑(左子树),再拆除右侧设施(右子树),最后才能动摇中央主体结构(根节点)。这种"自底向上"的访问方式在释放树形结构的内存时特别有用。
void postorder(TreeNode* root) {if (root == nullptr) return;postorder(root->left); // 遍历左子树postorder(root->right); // 遍历右子树cout << root->val << " "; // 访问根节点}
4. 层序遍历
层序遍历按照从上到下、从左到右的顺序访问树的节点,通常使用队列实现。
这就像参观一座多层建筑,你必须先参观完一层所有房间,才能乘电梯上到下一层。在每层内,你总是从左边开始,依次向右参观每个房间。
void levelOrder(TreeNode* root) {if (root == nullptr) return;queue<TreeNode*> q;q.push(root);while (!q.empty()) {TreeNode* node = q.front();q.pop();cout << node->val << " "; // 访问当前节点if (node->left) q.push(node->left); // 将左子节点入队if (node->right) q.push(node->right); // 将右子节点入队}}
三、二叉搜索树(BST)的操作
1. 查找操作
在BST中查找值为key的节点,如果找到则返回该节点,否则返回nullptr。
想象你在查找一本特定的书,在有序书架上,你可以通过比较书名来快速缩小范围 —— 如果目标书比当前查看的书名小,就去左边找;如果更大,就去右边找。这种"二分查找"的思路使得BST的查找非常高效。
TreeNode* search(TreeNode* root, int key) {// 基本情况:树为空或找到节点if (root == nullptr || root->val == key)return root;// 如果key小于当前节点值,去左子树查找if (key < root->val)return search(root->left, key);// 如果key大于当前节点值,去右子树查找return search(root->right, key);}
2. 插入操作
在BST中插入值为key的新节点。
就像在有序书架上添加新书,你需要找到正确的位置 —— 比较书名,决定是放在左边还是右边,直到找到一个空位置。
TreeNode* insert(TreeNode* root, int key) {// 如果树为空,创建新节点作为根节点if (root == nullptr)return new TreeNode(key);// 递归插入if (key < root->val)root->left = insert(root->left, key);else if (key > root->val)root->right = insert(root->right, key);// 如果key已存在,不做任何操作return root;}
3. 删除操作
从BST中删除值为key的节点,这是最复杂的操作,需要考虑多种情况。
删除一本书时,你需要考虑这本书在书架上的位置:如果它没有"关联书籍"(子节点),直接移除即可;如果它只有一侧有关联书籍,让这些书籍"上升一级";如果两侧都有关联书籍,则需要找到"最接近"的那本书来代替它的位置。
TreeNode* deleteNode(TreeNode* root, int key) {// 基本情况:空树if (root == nullptr) return root;// 寻找要删除的节点if (key < root->val)root->left = deleteNode(root->left, key);else if (key > root->val)root->right = deleteNode(root->right, key);else {// 找到了要删除的节点// 情况1:没有子节点或只有一个子节点if (root->left == nullptr) {TreeNode* temp = root->right;delete root;return temp;}else if (root->right == nullptr) {TreeNode* temp = root->left;delete root;return temp;}// 情况2:有两个子节点// 找到右子树中的最小节点(中序后继)TreeNode* temp = minValueNode(root->right);// 用中序后继的值替换当前节点的值root->val = temp->val;// 删除中序后继root->right = deleteNode(root->right, temp->val);}return root;}// 辅助函数:查找最小值节点TreeNode* minValueNode(TreeNode* node) {TreeNode* current = node;// 找到最左侧的节点while (current && current->left != nullptr)current = current->left;return current;}
四、平衡二叉树与旋转操作
1. 为什么需要平衡二叉树
当BST严重不平衡时(如退化成链表),其操作的时间复杂度会从O(log n)变为O(n),大大降低效率。
想象一个只有左侧书架的图书馆,所有书都排成一列,你必须从头到尾线性查找,无法利用二分查找的优势。这就是为什么我们需要保持树的平衡 —— 确保左右两侧的"高度"大致相当。
2. AVL树与红黑树
这两种都是自平衡的二叉搜索树,通过特定规则和旋转操作保持树的平衡。
它们就像有严格整理规则的智能书架系统,不断自我调整,确保任何时候都不会出现一边"堆积如山"而另一边"空空如也"的情况。
3. 旋转操作:左旋与右旋
旋转是保持二叉树平衡的基本操作。
想象一个机械书架系统,当发现左侧书太多时,会将部分书架向右旋转调整;当右侧过重时,则向左旋转调整。这些精密的"旋转"操作确保了整个系统的平衡性。
// 右旋操作TreeNode* rightRotate(TreeNode* y) {TreeNode* x = y->left;TreeNode* T2 = x->right;// 执行旋转x->right = y;y->left = T2;// 返回新的根节点return x;}// 左旋操作TreeNode* leftRotate(TreeNode* x) {TreeNode* y = x->right;TreeNode* T2 = y->left;// 执行旋转y->left = x;x->right = T2;// 返回新的根节点return y;}
五、二叉树的实际应用
1. 表达式解析与编译器设计
二叉树广泛应用于解析算术表达式,操作符作为节点,操作数作为叶子节点。
编程语言的编译过程中,源代码首先被解析成"语法树",这是一种特殊的树形结构,帮助编译器理解代码的逻辑结构,就像我们理解句子的语法结构一样。
2. 文件系统设计
计算机文件系统在逻辑上通常表现为树形结构,尽管物理存储可能不同。
你的电脑文件夹系统就是一个树形结构,根目录(如C盘)是树的根,每个文件夹可以包含文件(叶子节点)和子文件夹(子树),形成了一个直观易用的层次化存储系统。
3. 路由算法与网络设计
在网络路由中,决策树帮助路由器确定数据包的最佳转发路径。
数据包在互联网上传输,就像在一个巨大的迷宫中导航,路由器在每个"分叉点"(节点)做出选择,决定下一步走哪条路,这些决策路径形成了一个树形结构。
4. 游戏AI与决策树
电子游戏中的人工智能常使用决策树来模拟"思考过程"。
国际象棋AI思考下一步棋时,会构建一个决策树 —— 自己走A路线,对手可能会有B、C两种应对,对于每种应对我又有不同选择...这个"多步思考"的过程实际上就是在构建和评估一棵庞大的决策树。
六、高级二叉树结构与应用
1. Trie(前缀树)
Trie是一种特殊的树形数据结构,特别适合处理字符串集合,常用于自动补全和拼写检查。
想象一本字典,每个单词的字母沿着特定路径排列。如果多个单词有相同前缀(如"program"和"programming"),它们会共享相同的初始路径,既节省空间又加速查找。
struct TrieNode {bool isEndOfWord;TrieNode* children[26]; // 假设只有小写字母TrieNode() {isEndOfWord = false;for (int i = 0; i < 26; i++)children[i] = nullptr;}};// 插入操作void insert(TrieNode* root, string key) {TrieNode* pCrawl = root;for (int i = 0; i < key.length(); i++) {int index = key[i] - 'a';if (!pCrawl->children[index])pCrawl->children[index] = new TrieNode();pCrawl = pCrawl->children[index];}// 标记当前节点为单词结尾pCrawl->isEndOfWord = true;}// 查找操作bool search(TrieNode* root, string key) {TrieNode* pCrawl = root;for (int i = 0; i < key.length(); i++) {int index = key[i] - 'a';if (!pCrawl->children[index])return false;pCrawl = pCrawl->children[index];}return (pCrawl != nullptr && pCrawl->isEndOfWord);}
2. 线段树(Segment Tree)
线段树是一种用于处理区间查询和更新的树形数据结构。
设想一个长长的图书架,如果你想知道从第3本到第15本书的总价值,或者想更新第7本到第12本书的价格,线段树可以高效完成这些"区间操作",而不需要遍历每一本书。
3. B树与B+树
这两种树结构主要用于数据库索引和文件系统,能有效处理大量数据且支持高效的磁盘访问。
它们就像图书馆的多级索引系统,一个总索引指向各个主题索引,主题索引再指向具体书架,层层递进,使得即使在海量图书中也能快速定位到特定书籍。B+树特别适合范围查询,如"找出所有1990-2000年出版的书"。
七、优化与实战技巧
1. 空间优化技巧
在内存受限的环境中,可以通过多种方式优化二叉树的空间使用。
一种常见技巧是使用数组实现完全二叉树,对于索引为i的节点,其左子节点索引为2i+1,右子节点索引为2i+2。这就像在一排连续的格子中按特定规则存放物品,省去了存储"指向下一个格子"的指针,大大节省了空间。
2. 多线程环境下的二叉树操作
在并发编程中,需要特别关注二叉树的线程安全问题。
想象多个图书管理员同时操作同一个书架系统,如果没有协调机制,可能导致混乱 —— A正在调整第三层书籍时,B可能正在移动整个书架。通过锁机制、不可变数据结构或特殊的并发友好树型数据结构(如RCU-protected trees),可以安全地在多线程环境中使用树形数据结构。
3. 序列化与反序列化
在分布式系统或需要持久化存储时,能够将二叉树转换为线性结构(序列化)并能还原(反序列化)是关键能力。
这就像需要将一个立体拼图(树)拆解成一维序列以便邮寄,并确保收件人能按照特定规则重新组装成原来的立体结构。
// 二叉树的序列化(前序遍历方式)string serialize(TreeNode* root) {if (!root) return "X,"; // X表示null节点string result = to_string(root->val) + ",";result += serialize(root->left);result += serialize(root->right);return result;}// 二叉树的反序列化TreeNode* deserialize(string data) {queue<string> nodes;string val;// 将序列化字符串分割为节点值队列for (char c : data) {if (c == ',') {nodes.push(val);val.clear();} else {val.push_back(c);}}return buildTree(nodes);}TreeNode* buildTree(queue<string>& nodes) {string val = nodes.front();nodes.pop();if (val == "X") return nullptr;TreeNode* node = new TreeNode(stoi(val));node->left = buildTree(nodes);node->right = buildTree(nodes);return node;}
八、未来展望与高级话题
1. 量子计算对树结构的潜在影响
随着量子计算的发展,传统的树形数据结构可能需要重新设计以适应量子环境。
传统计算机处理树形结构时是按照特定路径遍历的,而量子计算具有同时探索多条路径的能力(量子叠加态),这可能彻底改变我们思考和实现树形数据结构的方式。
2. 神经网络与决策树的融合
机器学习中,决策树与神经网络各有优势,未来可能看到更多融合这两种方法的混合模型。
决策树提供清晰的决策路径但缺乏灵活性,神经网络具有强大的学习能力但缺乏可解释性。将两者结合的"神经决策森林"试图兼取两种方法的优点,创造既强大又可解释的AI模型。
结语
二叉树是计算机科学的经典数据结构,它不仅是算法设计的基础,也是解决各种实际问题的有力工具。从简单的二叉搜索树到复杂的B+树,从表达式解析到游戏AI,二叉树无处不在。
掌握二叉树不仅是理解一种数据结构,更是获得一种思维方式 —— 层次化、递归性思考问题的能力。这种思维方式将帮助你在编程道路上走得更远,解决更复杂的问题。
就像一棵树需要强壮的根系支撑茂盛的枝叶,扎实的数据结构基础是构建高效算法和系统的关键。希望本文能帮助你在C++数据结构的探索之旅中打下坚实基础,更自信地应对各种编程挑战。
相关文章:
C++数据结构与二叉树详解
前言: 在C编程的世界里,数据结构是构建高效程序的基石,而二叉树则是其中最优雅且应用广泛的数据结构之一。本文将带你深入理解二叉树的本质、实现与应用,助你在算法设计中游刃有余。 一、二叉树的基本概念 1. 什么是二叉树 二叉树…...
论文阅读:2023 arxiv Safe RLHF: Safe Reinforcement Learning from Human Feedback
总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 Safe RLHF: Safe Reinforcement Learning from Human Feedback https://arxiv.org/pdf/2310.12773 https://github.com/PKU-Alignment/safe-rlhf 速览 研究动机ÿ…...
C++11中的std::condition_variable
一、什么是条件变量? std::condition_variable 是C11标准库中提供的线程同步工具,用于在多线程环境中实现“等待-通知”机制。它的核心作用是让线程能够高效地等待某个条件成立,避免“忙等待”对CPU资源的浪费。 条件变量必须与std::mutex配…...
6.8.最小生成树
一.复习: 1.生成树: 对于一个连通的无向图,假设图中有n个顶点,如果能找到一个符合以下要求的子图: 子图中包含图中所有的顶点,同时各个顶点保持连通, 而且子图的边的数量只有n-1条࿰…...
QT中栅格模式探索
1、Qt中选择了栅格模式,如下图所示: 2、在进行整个大的UI界面布局时,需了解每个控件所需要选择的属性sizePolicy。 sizePolicy包含如下几种选择: 3、举个例子:此时整个UI界面,我采用了栅格模式…...
SLAM | 激光SLAM中的退化问题
在激光SLAM中,判断退化环境的核心是通过数学建模分析环境特征对位姿估计的约束能力。除了LOAM中提出的退化因子D外,还存在多种基于表达式和阈值设定的方法。以下是几种典型方法及其实现原理: 1. 协方差矩阵特征值分析 原理:通过分析点云协方差矩阵的特征值分布,判断环境中…...
C++入门基础:命名空间,缺省参数,函数重载,输入输出
命名空间: C语言是基于C语言的,融入了面向对象编程思想,有了很多有用的库,所以接下来我们将学习C如何优化C语言的不足的。 在C/C语言实践中,在全局作用域中变量,函数,类会有很多,这…...
tomcat 的安装与启动
文章目录 tomcat 服务器安装启动本地Tomcat服务器 tomcat 服务器安装 https://tomcat.apache.org/下载 Tomcat 10.0.X 启动本地Tomcat服务器 进入 Tomcat 的 bin...
C 语言中经典的数据结构
在 C 语言中,经典的数据结构通常包括以下几种,每种都有其特定的应用场景和实现方式: 1. 数组(Array) 定义:连续内存空间存储相同类型的数据。 特点:随机访问快(O(1))&am…...
算法-堆+单调栈
堆 首先堆在我们的Java中我们的是一个优先队列类 PriorityQueue 然后我们要弄最大堆和最小堆 最大堆: PriorityQueue<Integer> pq new PriorityQueue<Integer>((a, b) -> b - a); 最小堆: PriorityQueue<Integer> pq new P…...
物联网平台管理系统
物联网平台管理系统概述 物联网平台管理系统是物联网架构中的核心枢纽,承担着承上启下的关键作用。它向下连接各类物联网设备,实现设备的接入、管理与控制;向上为应用开发提供统一的数据接口和共性模块工具,支撑起各种丰富多彩的…...
STM32CubeMX-H7-15-SPI通信协议读写W25Q64
前言 SPI(Serial Peripheral Interface)通信协议是一种高速、全双工、同步的串行通信协议 本篇文章就使用W25Q64模块来学习SPI,包括软件SPI代码的编写,硬件SPI,中断SPI和DMASPI SPI的应用场景和模块 !这里是抄AI的&a…...
【软考】论devops在企业信息系统开发中的应用
摘要: 随着互联网的不断发展,各行各业都在建设自己的企业信息系统,而随着业务的不断升级和复杂化,系统的更新迭代速度越来越快,系统也越来越复杂。对于信息系统开发者,架构师,管理者,…...
生物化学笔记:医学免疫学原理22 肿瘤及肿瘤治疗
肿瘤及肿瘤治疗 免疫疗法 CAR-T细胞介绍...
JVM考古现场(二十二):降维打击·用二向箔优化内存模型
"警报!三维堆内存正在经历二维化坍缩!" 我腰间的玄铁令突然震动,在蜀山剑派的量子剑阵中投射出诡异的曼德博分形——这是三体文明发动降维打击的铁证! 楔子:二向箔奇点降临 昆仑镜监控日志: // …...
第三阶段面试题
Nginx nginx常用模块以及其功能 proxy模块,进行代理功能 ssl模块,进行HTTPS协议的使用 gzip模块,进行传输数据的压缩 upstream模块,进行反向代理时使用 static模块,静态资源进行访问的模块 cache模块࿰…...
操作系统-PV
🧠 背景:为什么会有 PV? 类比:内存(生产者) 和 CPU(消费者) 内存 / IO / 磁盘 / 网络下载 → 不断“生产数据” 例如:读取文件、下载视频、从数据库加载信息 CPU → 负…...
nuxt3路由切换页面出不来,刷新可以
nuxt3遇到一个奇怪的现象: 不管是router.push()跳转还是navigateTo()跳转,浏览器url变了,但是页面是空白的,没加载出来,刷新之后页面正常。 解决方案: <template>下的所有内容必须套在一个div里面...
Spring Boot配置文件优先级全解析:如何优雅覆盖默认配置?
📚 一、为什么需要了解配置文件优先级? 想象一下,你正在玩一个游戏🎮,游戏里有默认设置,但你可以通过不同的方式修改这些设置: 游戏内置的默认设置(就像Spring Boot的默认配置&…...
医院数据中心智能化数据上报与调数机制设计
针对医院数据中心的智能化数据上报与调数机制设计,需兼顾数据安全性、效率性、合规性及智能化能力。以下为系统性设计方案,分为核心模块、技术架构和关键流程三部分: 一、核心模块设计 1. 数据上报模块 子模块功能描述多源接入层对接HIS/LIS/PACS/EMR等异构系统,支持API/E…...
Linux之基础命令
Linux作为开源操作系统的代表,以其高效、灵活和强大的命令行工具闻名。无论是系统管理、开发调试还是日常使用,掌握基础命令都是与Linux系统交互的必备技能。本文整理了20个最常用的Linux基础命令,帮助新手快速入门。 目录 目录与文件导航文…...
【MATLAB代码例程】AOA与TOA结合的高精度平面地位,适用于四个基站的情况,附完整的代码
本代码实现了一种基于到达角(AOA) 和到达时间(TOA) 的混合定位算法,适用于二维平面内移动或静止目标的定位。通过4个基站的协同测量,结合最小二乘法和几何解算,能够有效估计目标位置,并支持噪声模拟、误差分析和可视化输出。适用于室内定位、无人机导航、工业监测等场景…...
PC主板及CPU ID 信息、笔记本电脑唯一 MAC地址获取
🥇 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 🎉 声明: 作为全网 AI 领域 干货最多的博主之一,❤️ 不负光阴不负卿 ❤️ 文章目录 PC主板及CPU ID 信息物理 MAC地址获取win11 新电脑 wmic 安装❤️ 欢迎一起学AI…...
RK3568笔记八十二: 利用AI生成的简单数据转发服务程序
若该文为原创文章,转载请注明原文出处。 测试AI编写代码能力,做了个简单的数据转发功能,后期想部署到服务器 功能相对简单,大概功能如下: 1、打开TCP服务端,等待客户端连接 2、客户端连接后发送ID:1234格式,服务端收到,解析出ID:1234并记录 3、相同的ID数据之间互…...
C++17 信号量模拟实现
C17 信号量模拟实现 一、实现原理 C17 标准库没有原生信号量(C20才有),但可以通过 std::mutex std::condition_variable 模拟实现。以下是核心逻辑: #include <mutex> #include <condition_variable>class CountingSemaphore { private:…...
web后端语言中篇
#作者:允砸儿 #日期:乙巳青蛇年 三月十八 笔者本来打算隔一天给它更完的,但是事情有点多这几天,实在是抱歉。废话不多说直接进入正题。 PHP流程控制语句 什么是流控:流程控制语句用于决定代码的执行顺序。 #注意流程控制语句…...
Spine-Leaf 与 传统三层架构:全面对比与解析
本文将详细介绍Spine-Leaf架构,深入对比传统三层架构(Core、Aggre、Access),并探讨其与Full-mesh网络和软件定义网络(SDN)的关联。通过通俗易懂的示例和数据中心网络分析,我将帮助您理解Spine-L…...
Vmware esxi 查看硬盘健康状况
起因 硬盘掉盘 - - 使用自带的命令esxcli 列出所有硬盘 esxcli storage core device list[rootlocalhost:~] esxcli storage core device list t10.NVMe____INTEL_MEMPEK1W016GAL____________________PHBT83660BYP016D____00000001Display Name: Local NVMe Disk (t10.NVMe…...
React 事件处理基础
React 中最常见的两个需求,一个是列表渲染,另一个就是绑定点击事件。 这一篇就是从最基础的按钮点击开始,分四个阶段,逐步理解 React 中事件的写法和参数传递方式。 📍阶段一:最简单的点击事件 function A…...
pandas库详解
CONTENT 基本数据结构SeriesDataFrame 数据读取与写入读取 CSV 文件写入 CSV 文件 数据清洗处理缺失值数据类型转换 数据操作索引与切片数据合并数据分组与聚合 数据可视化 基本数据结构 Series Series 属于一维标记数组,由一组数据和对应的索引构成。 import pa…...
