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

考研复习Day 16 | 数据结构与算法 --树与二叉树(上)

一、树的基本概念

1.1 树的定义

:n(n≥0)个结点的有限集。

  • 当 n=0 时,称为空树

  • 当 n>1 时,其余结点可分为 m 个互不相交的有限集,每个集合本身又是一棵树,称为根的子树

树的定义是递归的——在定义中引用了自身,因此树是一种典型的递归数据结构

树的逻辑特征

特征说明
根结点没有前驱,唯一
其他结点有且仅有一个前驱
所有结点可拥有零个或多个后继

重要结论:在包含 n 个结点的树中,恰好存在n-1 条边


1.2 基本术语

以图5.1中的树为例(根A,子结点B、C、D...):

术语说明示例
祖先从根到某结点路径上的所有结点B是K的祖先
子孙某结点子树中的所有结点K是B的子孙
双亲路径上最接近该结点的结点E是K的双亲
孩子双亲的直接后继K是E的孩子
兄弟具有相同双亲的结点K与L互为兄弟
堂兄弟双亲位于同一层的结点G与E、F、H、I、J互为堂兄弟
结点的层次根为第1层,其孩子为第2层,以此类推
结点的深度结点所在的层次
树的高度(深度)树中结点的最大层数
结点的高度以该结点为根的子树的高度
结点的度结点的孩子个数
树的度树中所有结点的度的最大值
分支结点(非终端结点)度 > 0 的结点
叶结点(终端结点)度 = 0 的结点
有序树子树从左到右有固定次序,不可互换
无序树子树无固定次序
路径从一个结点到另一个结点所经过的结点序列
路径长度路径上边的数目
森林m(m>0)棵互不相交的树的集合

森林与树的关系:将树的根结点移除后,其各子树构成一个森林;反之,为m棵独立的树添加一个新结点作为共同根,森林便转化为一棵树。


1.3 树的性质

性质公式说明
结点数与度数的关系n = 总度数 + 1除根结点外,每个结点都有唯一双亲
度为m的树第i层最多结点数m^(i-1)第1层1个,第2层m个,第3层m²个...
高度为h的m叉树最多结点数(m^h - 1)/(m - 1)各层结点数达到最大时
度为m、n个结点的树最小高度

树应尽可能饱满
度为m、n个结点的树最大高度n - m + 1至少一个结点有m个孩子,其他层仅一个结点

二、二叉树的概念

2.1 二叉树的定义及其主要特性

二叉树:每个结点至多拥有两棵子树(不存在度大于2的结点),且子树有明确的左右之分,次序固定,不可交换。

二叉树的5种基本形态

  1. 空二叉树

  2. 只有根结点

  3. 只有左子树

  4. 只有右子树

  5. 左右子树都有

二叉树与度为2的有序树的区别

  • 度为2的树至少3个结点,二叉树可以为空

  • 度为2的有序树中,只有一个孩子时无须区分左右;二叉树中左右位置是结构本身的固有属性


2.2 几种特殊的二叉树

特殊二叉树定义特点
满二叉树高度为h,包含2^h-1个结点每一层结点数都达到最大;所有叶结点在最下一层;除叶结点外,每个结点度均为2
完全二叉树高度为h,n个结点,与满二叉树中编号1~n的结点一一对应叶结点只出现在最后两层;最多只有一个度为1的结点
二叉排序树左子树所有结点 < 根结点 < 右子树所有结点左右子树本身也是二叉排序树
平衡二叉树任意结点左右子树高度差绝对值 ≤ 1
正则二叉树每个分支结点均有2个孩子树中仅有度为0或2的结点

完全二叉树的编号性质(编号从1开始):

性质公式
最后一个分支结点编号⌊n/2⌋
结点i的双亲编号⌊i/2⌋(i>1)
结点i的左孩子编号2i(若存在)
结点i的右孩子编号2i+1(若存在)
结点i所在层次⌊log₂i⌋ + 1
完全二叉树高度⌈log₂(n+1)⌉⌊log₂n⌋ + 1

2.3 二叉树的性质

性质公式说明
叶子结点与度为2的结点关系n₀ = n₂ + 1非空二叉树
第k层最多结点数2^(k-1)k≥1
高度为h的二叉树最多结点数2^h - 1

性质1的证明:设n₀、n₁、n₂分别为度为0、1、2的结点数,n = n₀ + n₁ + n₂。分支数B = n - 1 = n₁ + 2n₂,代入得n₀ = n₂ + 1。


2.4 二叉树的存储结构

1. 顺序存储结构

使用一组连续的存储单元,按层序(自上而下、自左至右)存储二叉树结点。

适用场景:完全二叉树和满二叉树最适用,能最大限度节省存储空间。

缺点:一般二叉树需插入“空结点”补全为完全二叉树形状,最坏情况(单支树)需要近2^h-1个存储单元,空间利用率极低。

建议:从数组下标1开始存储,保证数组下标与结点编号一致。

2. 链式存储结构(二叉链表)

typedef struct BiTNode { ElemType data; struct BiTNode *lchild, *rchild; } BiTNode, *BiTree;

重要结论:在含有n个结点的二叉链表中,共有n+1个空链域(常考选择题)。


三、二叉树的遍历

3.1 三种递归遍历

遍历方式访问顺序遍历序列(图5.7二叉树)
先序遍历(PreOrder)根 → 左 → 右1,2,4,6,3,5
中序遍历(InOrder)左 → 根 → 右2,6,4,1,3,5
后序遍历(PostOrder)左 → 右 → 根6,4,2,5,3,1

递归算法模板

// 先序遍历 void PreOrder(BiTree T) { if (T != NULL) { visit(T); // 访问根结点 PreOrder(T->lchild); PreOrder(T->rchild); } } // 中序遍历 void InOrder(BiTree T) { if (T != NULL) { InOrder(T->lchild); visit(T); InOrder(T->rchild); } } // 后序遍历 void PostOrder(BiTree T) { if (T != NULL) { PostOrder(T->lchild); PostOrder(T->rchild); visit(T); } }

时间复杂度:O(n)(每个结点访问一次)
空间复杂度:O(h)(递归工作栈深度等于树高,最坏情况O(n))


3.2 层次遍历

借助队列,从上到下、从左到右逐层访问。

void LevelOrder(BiTree T) { Queue Q; InitQueue(Q); BiTree p = T; if (p != NULL) EnQueue(Q, p); while (!QueueEmpty(Q)) { DeQueue(Q, p); visit(p); if (p->lchild != NULL) EnQueue(Q, p->lchild); if (p->rchild != NULL) EnQueue(Q, p->rchild); } }

3.3 由遍历序列构造二叉树

核心原则:必须知道中序序列,再辅以先序、后序或层序中的任意一种,才能唯一确定一棵二叉树。

已知序列组合是否能唯一确定二叉树
先序 + 中序
后序 + 中序
层序 + 中序
先序 + 后序不能
先序 + 层序不能
后序 + 层序不能

构造方法

组合方法
先序+中序先序第一个为根 → 在中序中划分左右子树 → 递归
后序+中序后序最后一个为根 → 在中序中划分左右子树 → 递归
层序+中序层序第一个为根 → 在中序中划分左右子树 → 层序中后续结点依次为各子树的根

四、线索二叉树

4.1 线索二叉树的基本概念

背景:在n个结点的二叉树中,有n+1个空链域。利用这些空指针指向遍历序列中的前驱或后继,称为线索化

结点结构

标志位含义
ltag = 0lchild指向左孩子
ltag = 1lchild指向前驱线索
rtag = 0rchild指向右孩子
rtag = 1rchild指向后继线索

存储结构定义

typedef struct ThreadNode { ElemType data; struct ThreadNode *lchild, *rchild; int ltag, rtag; } ThreadNode, *ThreadTree;

4.2 中序线索二叉树的构造

pre指向刚刚访问过的结点,p指向当前正在访问的结点。

void InThread(ThreadTree &p, ThreadTree &pre) { if (p != NULL) { InThread(p->lchild, pre); // 递归线索化左子树 if (p->lchild == NULL) { // 左孩子为空,建立前驱线索 p->lchild = pre; p->ltag = 1; } if (pre != NULL && pre->rchild == NULL) { // 前驱的右孩子为空,建立后继线索 pre->rchild = p; pre->rtag = 1; } pre = p; // 标记当前结点为刚访问过的结点 InThread(p->rchild, pre); // 递归线索化右子树 } } void CreateInThread(ThreadTree T) { ThreadTree pre = NULL; if (T != NULL) { InThread(T, pre); pre->rchild = NULL; // 处理最后一个结点 pre->rtag = 1; } }

4.3 中序线索二叉树的遍历

找中序第一个结点

ThreadNode *FirstNode(ThreadNode *p) { while (p->ltag == 0) p = p->lchild; // 最左下结点 return p; }

找中序后继

ThreadNode *NextNode(ThreadNode *p) { if (p->rtag == 0) return FirstNode(p->rchild); // 右子树的最左下结点 else return p->rchild; // 直接返回后继线索 }

非递归中序遍历

void InOrder(ThreadNode *T) { for (ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p)) visit(p); }

4.4 先序和后序线索二叉树

遍历方式查找后继的复杂度
先序线索较简单:有左孩子则左孩子为后继,否则右孩子为后继,否则线索直接指向
后序线索较复杂:需要知道双亲信息,通常需用三叉链表

后序线索查找后继的三种情况

情况后继
结点是根后继为空
结点是双亲的右孩子,或是双亲的左孩子且双亲无右孩子后继为双亲
结点是双亲的左孩子且双亲有右子树后继为右子树中后序第一个结点

五、思考

1. 树 ≈ 文件系统

电脑文件夹结构就是一棵树:根目录是根结点,子文件夹是子树,文件是叶子结点。

2. 递归遍历 ≈ 俄罗斯套娃

每打开一个套娃,里面还有一个更小的套娃,直到最小的那个(空树)。递归就是这种“自己包含自己”的结构。

3. 层次遍历 ≈ 逐层打印

就像BFS(广度优先搜索),一层一层地处理,先处理根,再处理所有孩子,再处理所有孙子。

4. 线索二叉树 ≈ 链表的“快速跳转”

把二叉树变成链表一样的结构,遍历时不需要递归,直接沿着线索走。

注:以上内容参考 2027年数据结构考研复习指导 王道论坛 组编,其中有一些个人想法,如有任何错误或不妥,欢迎各位大佬指出,如果各位有一些有意思的想法,也可以和我交流一下~感谢!

六、一点题外话

学习了二叉树,脑海中不由得浮现出了中学学过的一篇课文:美国诗人罗伯特·弗罗斯特创作的《未选择的路》:

黄色的树林里分出两条路,

可惜我不能同时去涉足,

我在那路口久久伫立,

我向着一条路极目望去,

直到它消失在丛林深处。

但我却选了另外一条路,

它荒草萋萋,十分幽寂,

显得更诱人、更美丽,

虽然在这条小路上,

都很少留下旅人的足迹,

虽然那天清晨落叶满地,

两条路都未经脚印污染。

啊,留下一条路等改日再见!

但我知道路径延绵无尽头,

恐怕我难以再回返。

也许多少年后在某个地方,

我将轻声叹息把往事回顾,

一片树林里分出两条路,

而我选了人迹更少的一条,

因此走出了这迥异的旅途。

1. 二叉树的结构 = 人生的“分岔点”

每一个选择,都是一个结点;选择的结果,决定你走向哪一棵子树

我们的人生,就是一棵巨大的、不断生长的二叉树。每一个自己,都是由无数次“左拐还是右拐”塑造出来的。

2. 路径的“不可逆性” = 人生没有撤回键

“啊,留下一条路等改日再见!但我知道路径延绵无尽头,恐怕我难以再回返。”

二叉树中,一旦你选择了左子树,就无法再回到父结点去走右子树(除非有父指针)。

人生也一样:你选择了一条路,就不可能同时体验另外那条路。

这就是选择的代价,也是选择的庄严

3. “成功路不止一条” = 二叉树有多条路径到达叶结点

“这些路径中有很多都是可以通向成功的,成功的路不止一条。”

二叉树中,从根到任何一个叶结点的路径,都是一条完整的、独特的旅途。没有哪条路径是“标准答案”,也没有哪条路径是“绝对的错误”。只要最终到达了你想去的“叶结点”,这条路径就是对的。


4.“受挫折时想二叉树”

受到挫折时候想一想二叉树,只要没有结束,就永远会有翻盘的机会。

二叉树的高度 ≠ 成功的早晚

二叉树中,从根到叶结点的路径长度可以完全不同

  • 有的叶结点在第3层(早早成功)

  • 有的叶结点在第10层(大器晚成)

只要还没到NULL(放弃),你就还在遍历中。

人生就像是二叉树,是在无数次的选择中成长的,选择决定了我是怎么样的一个我。只要没有结束,永远会有翻盘的机会。这些路径中有很多都是可以通向成功的,成功的路不止一条。


七、明日计划

树与二叉树(下)— 树、森林与二叉树的转换、哈夫曼树

http://www.jsqmd.com/news/668830/

相关文章:

  • AI Agent Harness Engineering 的部署架构:单体部署、分布式部署与混合云
  • 终极BT下载加速指南:每天更新的Tracker列表让你的下载速度翻倍
  • FastAPI 项目 PyInstaller 打包 exe 全踩坑根治教程(Windows 全电脑通用分发)
  • 企业云盘选型标准合同条款:数据归属/服务等级/SLA全解析
  • 探究分享从对话到执行:OpenTiny NEXT 如何重塑前端智能化开发范式
  • STM32 IAP升级踩坑实录:BootLoader跳转失败、向量表重置、Flash分区冲突,我是如何解决的?
  • ControlSizePyQt - PyQt 版本的统一尺寸和颜色管理系统
  • 网络工程师必看:H3C与华为认证体系的前世今生及备考选择指南
  • 淘一个二手铷原子钟并用起来的过程
  • 从卖不出去到月入15000,贵阳这两家公司凭什么让销售翻身? - 精选优质企业推荐官
  • 一文看懂推荐系统:排序09:Field-aware Factorization Machines (FFM) 的工业界冷思考:为何从FM到FFM的改进叫好不叫座?
  • uni-app怎么实现弹窗 uni-app自定义模态框遮罩层【代码】
  • ESP32上传图片到巴法云,除了HTTPClient,你还可以试试这个库
  • 频谱分析仪
  • Qt Quick项目实战:用KDDockWidgets 1.4.0为你的QML界面添加可拖拽停靠面板(附源码)
  • C语言学习日志
  • 学习分享数据结构对比
  • Spring Boot 自动装配原理(面试版 + 实战理解版)
  • 老年人扎堆学AI,背后藏着千亿级银发经济新蓝海
  • 别再让Quartus默认的1GHz时钟坑了你!手把手教你为FPGA点灯工程写SDC约束文件
  • 通风系统节能改造笔记:用PLC分段控制替代PID,稳定风压还省电(含现场数据对比)
  • 【2026年最新600套毕设项目分享】微信小程序的小说实体书商城(30106)
  • RKNN模型在RK3588上初始化失败?别慌,可能是你的虚拟环境和开发板版本对不上
  • AI开发-python-langchain框架(--pdf文件分页加载 )
  • Polkadot 技术栈地图 2026
  • 【计算机网络 实验报告6】路由选择协议
  • 从H264到H266:视频编码的‘乐高’块是如何越变越小的?一个动画演示看懂核心差异
  • 千问模型本地部署
  • 万字长文爆肝:彻底弄懂Linux文件系统(Ext2),从Inode、Block到Dentry核心机制全解析
  • 贵阳求职市场大洗牌:为什么AI营销和顾问型销售正在成为新的职业风口? - 精选优质企业推荐官