例题4-3:判定二叉树(顺序存储)是否为完全二叉树
← 内容总览 16 / 24 下一节 →
强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 例题4-3:判定二叉树(顺序存储)是否为完全二叉树
遍历方式选择:完全二叉树的定义是按层序从左到右填充的。这天然地指向了层序遍历。在遍历过程中,一旦我们遇到了一个空结点,那么之后的所有结点都必须是空结点,否则就不是完全二叉树。特别注意:题目指定了“顺序存储”(即数组)。对于数组表示的二叉树,判断更简单。如果树有n个结点,那么它必须占据数组的前n个位置,中间不能有“空洞”。我们只需要找到最后一个非空结点的位置k,然后检查从0到k是否都有结点即可。这本质上是一种简化的线性扫描,可以看作层序思想的应用。
## visit(p)功能实现(基于队列的层序遍历):
1. 使用一个队列,根结点入队。
2. 设置一个标志位 bool leaf = false;。
3. 循环直到队列为空:
结点p出队。
如果 eaf 为 true,但p却有子结点(左或右),则该树不是完全二叉树,返回false。
如果 p有右孩子但没有左孩子,直接返回 false。
如果p只有左孩子或没有孩子,将 leaf设为 true。
将p的非空子结点入队。
4. 循环正常结束,返回 true
## 例题4-4:判定二叉树是否为二叉排序树 (BST)
遍历方式选择:二叉排序树(BST)的一个核心性质是:它的中序遍历序列是一个严格递增的序列。这是判断BST的最直接、最简单的方法。 X
visit(p)功能实现:我们需要一个变量pre\_value 来记录中序遍历中“上一个”被访问的结点的值。
1. 初始化 pre\_value 为一个极小值(比如 INT\_MIN)。
2. 进行中序遍历。
3. visit(p)的逻辑是:
比较当前结点 p->value 和 pre\_value。
如果 p->value <= pre\_value,则不满足递增性质,该树不是 BST,返回false。
如果满足,则更新 pre\_value = p->value,然后继续遍历。
4. 如果整个遍历都成功,返回 true
## 例题4-5:统计森林的叶结点数
遍历方式选择:森林是多棵树的集合。我们可以对森林中的每一棵树分别进行遍历。对于每一棵树,求其叶子结点数的方法和例题4-1完全一样。因此,对每棵树可以使用任意遍历方式(先序、中序、后序均可)。
<!-- page 160 -->
visit(p)功能实现:与例题4-1 完全相同。
1. 设置一个全局计数器 leaf\_count = 0;。
2. 对森林中的每一棵树,调用求叶子数的函数(该函数内部进行遍历)。
3. visit(p)的逻辑是:检查p是否为叶子结点,如果是,则 leaf\_count++。
## 例题4-6:判定二叉排序树是否为平衡二叉树 (AVL)
遍历方式选择:平衡二叉树的定义是:对于任意结点,其左、右子树的高度差的绝对值不超过1。要判断结点p是否平衡,你必须先知道其左、右子树的高度。这又是一个典型的“自底向上”的计算,因此必须使用后序遍历。
visit(p)功能实现:这个实现结合了求树的高度(例题4-2)和判断。我们可以设计一个递归函数,它返回子树的高度;如果发现不平衡,则返回一个特殊值(如-1)作为标记。 小
<table><tr><td rowspan=1 colspan=1>例题 </td><td rowspan=1 colspan=1>核心问题</td><td rowspan=1 colspan=1>最佳遍历方式</td><td rowspan=1 colspan=1>visit(p)/核心操作</td></tr><tr><td rowspan=1 colspan=1>4-1:求叶子数</td><td rowspan=1 colspan=1>结点自身属性</td><td rowspan=1 colspan=1>先序/中序/后序遍历</td><td rowspan=1 colspan=1>判断p是否为叶子,是则计数器加一</td></tr><tr><td rowspan=1 colspan=1>4-2: 求深度</td><td rowspan=1 colspan=1>依赖子树结果</td><td rowspan=1 colspan=1>后序遍历粥</td><td rowspan=1 colspan=1>1 + max(左子树深度,右子树深度)</td></tr><tr><td rowspan=1 colspan=1>4-3: 是否完全二叉树</td><td rowspan=1 colspan=1>层次结构/无间隙</td><td rowspan=1 colspan=1>层序遍历</td><td rowspan=1 colspan=1>检查遇到第一个空位后,是否还有非空结点</td></tr><tr><td rowspan=1 colspan=1>4-4: 是否二叉排序树</td><td rowspan=1 colspan=1>í</td><td rowspan=1 colspan=1>中序遍历</td><td rowspan=1 colspan=1>比较 p->value与前一个结点的值,判断是否递增</td></tr><tr><td rowspan=1 colspan=1>4-5: 森林叶子数</td><td rowspan=1 colspan=1>遍历多棵树</td><td rowspan=1 colspan=1>先根/后根遍历</td><td rowspan=1 colspan=1>同4-1,对森林中每棵树分别统计</td></tr><tr><td rowspan=1 colspan=1>4-6: 是否平衡二叉树</td><td rowspan=1 colspan=1>依赖子树高度</td><td rowspan=1 colspan=1>后序遍历</td><td rowspan=1 colspan=1>计算左右子树高度差,判断是否 > 1</td></tr></table>
<!-- page 161 -->
## 4.4 并查集
并查集是一种高效支持“集合合并”与“查找代表元”的数据结构,常用于处理图中连通性判断、集合合并问题,例如判断是否构成树、求连通块数量、判断环等问题。在图那一章中介绍其应用。
Father 数组: [1, 1, 1, 3, 1, 5, 3]
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
并查集使用数组 father[]存储每个元素的“父结点”(其实是祖先结点),形成森林结构。为了方便使用,通常 father数组定义成全局变量。
find(x):查找下标为x的结点所在树的树根下标,可路径压缩(把查找途中所有结点全部变成根结点的儿子,减少树的高度)。
Union(x, y):将下标为 x、y的结点所在树合并。
<!-- verified-by-hand -->
> [!success] 已结合原文第 161 页及 Markdown 上下文人工校对
> 注释缺少第二个斜杠,Union 函数丢失父结点赋值和闭合花括号。
int father [ MAXN ]; // father[i] 表示 i 所在集合的代表元(根)
int find ( int x )
{
if ( father [ x ] != x )
father [ x ] = find ( father [ x ]);
return father [ x ];
}
void Union ( int x , int y )
{
int fx = find ( x ), fy = find ( y );
if ( fx != fy )
father [ fy ] = fx ;
}
<!-- page 163 -->
## 4.5 二叉搜索树
数据结构定义同二叉链表表示法(命名规则:二叉树是BTNode,二叉搜索树是BSTNode),只是保证权值:左<中<右。
## 4.5.1 数据结构定义
typedef struct BSTNode
{
ElemType data ;
struct BSTNode * lchild , * rchild ;
} BSTNode , * linklist ;
## 4.5.2 查找
BTNode * BST_Search ( BSTNode * T , ElemType x )
{
BSTNode * p = T ;
while ( p != NULL )
{
if ( x == p -& gt ; data )
return p ;
if ( x & lt ; p -& gt ; data )
p = p -& gt ; lchild ;
else
p = p -& gt ; rchild ;
}
return NULL ;
}
查找的时间复杂度是O(h),h是高度,最坏情况就是O(n)。对于二叉搜索树,最多也就是考察查找了,但不一定只会查找一次,可能需要查找多次。如果我们实在不会写二叉搜索树的查找怎么办呢,回忆一下:是不是所有树的问题都可以用遍历来做,所以你也可以对二叉搜索树进行遍历查找,时间复杂度也是O(n)。
## 4.6 章节练习
## 4.6.1 习题
【习题4-1】有 n 个结点的完全二叉树的权值存放在一维数组 A[n] 中(从 A[0] 开始存放),试设计一个算法,将该完全二叉树用二叉链表表示,算法返回指向根结点的指针。二叉树结点数据类型定义如下:
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 163 页)
> OCR 体检未通过:花括号不平衡。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
typedef struct node {
int data ;
<!-- page 164 -->
(1) 给出算法的基本设计思想。(2)根据设计思想,采用 C 或 C++ 语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。
<!-- page 165 -->
【习题4-2】设计一个算法,将一棵以二叉链表存储的二叉树T按顺序方式存储到一维数组B[]中(根结点下标为1,结点的权值>=0,数组值等于-1表示该结点不存在)。二叉树结点的数据类型定义如下:
typedef struct node {
int data ;
struct node * lchild , * rchild ;
} BTNode ;
(1) 给出算法的基本设计思想。(2) 根据设计思想,采用C 或C++ 语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。
<!-- page 166 -->
【\*习题4-3】已知由一棵二叉链表表示的二叉树,设计算法,输出二叉树的高度(只有根结点的二叉树高度为1)。二叉树结点数据类型定义如下:
typedef struct node {
int data ;
struct node * lchild , * rchild ;
} BTNode ;
(1)给出算法的基本设计思想,关键之处给出注释。(2) 根据设计思想,采用C或 C++语言描述算法。(3)说明所设计算法的时间复杂度。
<!-- page 167 -->
【习题4-4】设树T采用孩子兄弟链表表示(数据域、孩子域、兄弟域),设计算法计算树T的度。(1)写出树结点的数据结构定义。(2)给出算法的基本设计思想。(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释(4)说明所设计算法的时间复杂度。 成村
<!-- page 168 -->
【习题4-5】设二叉树以二叉链表形式存放,根结点的深度为1,一棵二叉树的繁茂程度定义为各层结点数与该层深度的乘积之和。试设计算法,求二叉树的繁茂程度。数据类型定义如下:
typedef struct node {
int data ;
struct node * lchild , * rchild ;
} BTNode ;
(1)给出算法的基本设计思想。(2)根据设计思想,采用C 或C++ 语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度和空间复杂度。
<!-- page 169 -->
【习题4-6】给定一棵二叉树,设计算法检查它是否是镜像对称的,如果是镜像对称则返回1,否则返回0。例如下图所示就是镜像对称的。 XEh
原资料配图 · 转写内容请回源核验
设计思想,采用C 或C++语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。
(1)给出算法的基本设计思想。(2) 根据
<!-- page 170 -->
二叉树结点定义如下:
【习题4-7】(13 分)二叉树中两个结点p 和q的最低公共祖先是二叉树中离根结点尽可能远且同时是p和q祖先的结点,结点的祖先可以是自己。给定一棵非空二叉树T,采用二叉链表存储。例如,对于下图所示的非空二叉树T,结点3和结点4的最低公共祖先是结点3,结点2和结点4的最低公共祖先是结点1。 成村
原资料配图 · 转写内容请回源核验
typedef struct node
{
int data ;
struct node * left , * right ;
} BTree ;
root 为指向T的根结点的指针,请设计算法求出p和 q的最低公共祖先的地址,要求:1)给出算法的基本设计思想。2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
<!-- page 171 -->
## 4.6.2 真题
【2014年41题】(13分)二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和。给定一棵二叉树T,采用二叉链表存储,结点结构如下:
<table><tr><td>left</td><td>weight</td><td>right</td></tr></table>
其中叶结点的 weight 域保存该结点的非负权值。设 root 为指向T的根结点的指针,请设计求T的WPL的算法,要求:(1)给出算法的基本设计思想。(2)使用C或C++语言,给出二叉树结点的数据类型定义。 (3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
<!-- page 172 -->
【2017年41题】(15分)请设计一个算法,将给定的表达式树(二叉树)转换为等价的中缀表达式(通过括号反映操作符的计算次序)并输出。例如,当下列两棵表达式树作为算法的输入时,输出的等价中缀表达式分别为(a+b)×(c×(-d))和(a×b)+(-(c-d))。
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
二叉树结点定义如下:
typedef struct node {
char data [ 10 ]; // 存储操作数或操作符
struct node * left , * right ;
} BTree ;
要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C或C++ 语言描述算法,关键之处给出注释。 XV
<!-- page 173 -->
【2022年41题】(13分)已知非空二叉树T的结点值均为正整数,采用顺序存储方式保存,数据结构定义如下:
typedef struct { //MAX_SIZE为已定义常量
int SqBiTNode [ MAX_SIZE ]; //保存二叉树结点值的数组
int ElemNum ; // 实际占用的数组元素个数
} SqBiTree ; // 首
T中不存在的结点在数组 SqBiTNode 中用-1 表示。例如,对于下图所示的两棵非空二叉树T1 和 T2,T1 的存储结果如下: 人
<table><tr><td rowspan=1 colspan=1>40</td><td rowspan=1 colspan=1>25</td><td rowspan=1 colspan=1>60</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>30</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>80</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>27</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1></td></tr></table>
T2 的存储结果如下:
<table><tr><td rowspan=1 colspan=1>qBiTNode</td><td rowspan=1 colspan=1>40</td><td rowspan=1 colspan=1>50</td><td rowspan=1 colspan=1>60</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>30</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>-1</td><td rowspan=1 colspan=1>35</td><td rowspan=1 colspan=1></td></tr></table>
请设计一个尽可能高效的算法,判定一棵采用这种方式存储的二叉树是否为二叉搜索树,若是,则返回true,否则,返回false。要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用 C 或 C++ 语言描述算法,关键之处给出注释。
<!-- page 174 -->
【2026年41题】回忆版(13分)假定二叉搜索树使用二叉链表存储,存储结构如下:
typedef struct BSTNode {
int data ;
struct BSTNode * left , * right ;
} BSTNode ;
typedef BSTNode BTNode ;
给了一个数k,求二叉树节点值和k的差的绝对值的最小值。设计尽可能高效的算法searchX(BTtree root,int k)输出最小值并输出所有最小值节点的关键字值。(1)给出算法的基本设计思想。(2)根据设计思想,采用C/C++描述算法,关键之处给出注释。
## 4.7 章节答案
## 4.7.1 习题
## 习题4-1
有 n 个结点的完全二叉树的权值存放在一维数组 A[n] 中 (从 A[0] 开始存放),试设计一个算法,将该完全二叉树用二叉链表表示,算法返回指向根结点的指针。二叉树结点数据类型定义如下: K
typedef struct node {
int data ;
struct node * lchild , * rchild ;
} BTNode ;
(1) 给出算法的基本设计思想。(2) 根据设计思想,采用C 或 C++ 语言描述算法,关键之处给出注释。(3)说明所设计算法的时间复杂度。人
【解析】对这棵树采用先中后序皆可,下面采用先序遍历,注意是结点从A[0]还是A[1]开始存放。【答案】(1)对完全二叉树从根结点开始先序遍历构建树,下标为i的结点左子树下标为i×2+1,他的右子树下标为i×2+2,当访问的结点下标i超过 n-1 时说明该结点为空。(2)代码如下:
BTNode * PreOrder ( int A [], int i , int n ) // i是当前访问下标
{
if ( i & gt ; = n || i & lt ; 0 )
return NULL ; // 该结点不存在
BTNode * p = ( BTNode * ) malloc ( sizeof ( BTNode ));
p -& gt ; data = A [ i ]; // 访问当前结点visit(p)
p -& gt ; lchild = Pre0rder ( A , i * 2 + 1 , n );
p -& gt ; rchild = Pre0rder ( A , i * 2 + 2 , n );
return p ;
}
<!-- page 175 -->
(3) 时间复杂度:O(n)
← 内容总览 16 / 24 下一节 →