(3) 时间复杂度:O(n)
原始讲义 · 非增强校订版 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## (3) 时间复杂度:O(n)
<!-- code-audit: false_positive -->
> [!info] 已人工审读:这是正文思考题及答案说明,被错误放入代码围栏。
请大家思考,如果根结点下标为0应该怎么写?
这种情况下,根结点下标为0,i号结点左孩子下标为2×i+1,右孩子下标为2×i+2
## 习题4-3
已知由一棵二叉链表表示的二叉树,设计算法,输出二叉树的高度(只有根结点的二叉树高度为1)。二叉树结点数据类型定义如下:
<!-- page 125 -->
typedef struct node{
int data;
struct node *lchild, *rchild;
}BTNode;
(1)给出算法的基本设计思想,关键之处给出注释。
(2) 根据设计思想,采用C或C++语言描述算法。
(3)说明所设计算法的时间复杂度。
【答案】 (1)对二叉树进行先序/中序/后序遍历,遍历时设置变量depth 保存当前子树的高度= max(左子树高度,右子树高度)+1,最后输出整棵树的高度即可。
(2) 代码如下:
int PostOrder(BTNode* p) //后序遍历
{
if (p==NULL)
return 0; // 该结点不存在
int L=Postorder(p->lchild);
int R=Postorder(p->rchild);
int depth=max(L, R)+1; // 访问当前结点visit(p)
return depth;
}
(3) 时间复杂度:O(n)
## 习题4-4
设树T采用孩子兄弟链表表示(数据域、孩子域、兄弟域),设计算法计算树T的度。
(1)写出树结点的数据结构定义。
(2) 给出算法的基本设计思想。
(3)根据设计思想,采用C或C++ 语言描述算法,关键之处给出注释
(4)说明所设计算法的时间复杂度。
【答案】(1)定义如下:
typedef struct node{
int data;
struct node *firstchild, *nextbro;
}TNode;
(2)核心思想:一个父结点的度,等于其第一个孩子结点的兄弟链表的长度。
算法通过一个后根序的递归函数 Post(p) 遍历树,该函数的主要功能是计算并返回以结点 p 开头的兄弟链表的长度。同时,用一个全局变量 deqree记录在整个计算过程中遇到的最长的兄弟链长度。
(3) 代码如下:
<!-- page 126 -->
(2)根据设计思想,采用C或C++; // 语言描述算法,关键之处给出注释。
int degree=0;
int Post(TNode* p)
{
if (p==NULL)
return 0;
Post(p->firstchild);
int d=1+Post(p->nextbro);
degree=max(degree, d);
return d;
}
## (4) 时间复杂度:O(n)
## 树的度:递归算法可视化
原资料配图 · 转写内容请回源核验
## 习题4-5
设二叉树以二叉链表形式存放,根结点的深度为1,一棵二叉树的繁茂程度定义为各层结点数与该层深度的乘积之和。试设计算法,求二叉树的繁茂程度。数据类型定义如下:
typedef struct node{
int data;
struct node*lchild, *rchild;
}BTNode;
<!-- page 127 -->
【答案】(1)按照二叉树的层次遍历 BFS,front 保存队头元素后一个位置下标,rear 保存队尾元素下标,last 保存上一层的最后一个元素下标,当本层访问完的时候,本层元素个数为rear-last,依次算出每一层的繁茂程度,并求出最大值。(2) 代码如下:
int ans(BTNode *T)
{
BTNode* q[MAX_SIZE];
int front=0, rear=0, depth=1;
int 1ast=0; // 本层的最后一个结点下标
if (T!=NULL) q[rear++]=T;
BTNode *p; // 当前访问的结点p
int sum=1*1; // 第一层的繁茂程度
while(front<rear)
{
p=q[front++];
if (p->lchild!=NULL)
q[rear++]=p->lchild;
if (p->rchild!=NULL)
q[rear++]=p->rchild;
if (front>last) // 该层已访问完
depth++;
printf("%d %d %d\n", front, rear, depth);
sum+=(rear-front)*depth;
last=rear-1;
}
return sum;
}
(3) // 时间复杂度:O(n) 空间复杂度:O(n)
## 习题4-6
给定一棵二叉树,设计算法检查它是否是镜像对称的,如果是镜像对称则返回1,否则返回0。例如下图所示就是镜像对称的。
<!-- page 128 -->
原资料配图 · 转写内容请回源核验
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(3)说明所设计算法的时间复杂度。
【解析】本题采用先序/中序/后序遍历都可以,设置两个指针p、q访问即可。
【答案】(1)按照先序遍历,在遍历的时候设置两个指针p、q,分别指向当前访问结点和它对称的位置,每次比较p和q的值是否相同。当p递归左(右)孩子的时候,q递归右(左)孩子,这样可以保证p和q一直是对称位置,如果发现p 和 q的值不一样则返回0。
(2) 代码如下:
int PreOrder(BTNode *p,BTNode *q)
{
if (p==NULL && q==NULL)
return 1;
if (p==NULL || q==NULL)
return 0;
if (p->data!=q->data)
return 0;
if (Preorder(p->lchild, q->rchild)==0)
return 0;
if (Preorder(p->rchild, q->lchild)==0)
return 0;
return 1;
}
(3)时间复杂度:O(n)
## 习题4-7
(13 分) 二叉树中两个结点p和g 的最低公共祖先是二叉树中离根结点尽可能远且同时是p 和 g 祖先的结点,结点的祖先可以是自己。给定一棵非空二叉树T,采用二叉链表存储。例如,对于下图所示的非空二叉树T,结点3和结点4的最低公共祖先是结点3,结点2和结点4的最低公共祖先是结点1。
二叉树结点定义如下:
<!-- page 129 -->
typedef struct node
{
int data;
struct node *left, *right;
}BTree;
root 为指向T的根结点的指针,请设计算法求出p和q的最低公共祖先的地址,要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
## 【答案】
(1) 算法设计思想:
使用递归后序遍历二叉树,对于当前访问的结点root:
• 若 root == p 或 root == q ,直接返回 root ;
• 递归分别在左子树、右子树查找 p 和 q ;
• 若左右子树递归结果均不为 NULL,说明 p 和 q 分别在左右子树中,root 即为最近公共祖先;
• 若其中一个子树递归结果为NULL,则返回另一侧的结果(说明两结点均在某一子树中)。
(2) 算法代码:
BTree* PostOrder(BTree* root, BTree* p, BTree* q) {
if (root == NULL)
return NULL;
if (root == p || root == q)
return root; // 若当前结点等于p或q,直接返回
BTree* left = PostOrder(root->left, p, q); //在左子树查找
BTree* right = PostOrder(root->right, p, q); // 在右子树查找
if (left != NULL && right != NULL)
return root; //左右子树都找到,当前root即为最近公
共祖先
//如果只在一个子树找到则返回该子树
if (left == NULL) return right;
else return left;
}
(3) 时间复杂度:O(n)
空间复杂度:O(n)
## 4.6.2 真题
## 2014年42题
(13分)二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和。给定一棵二叉树T,采用二叉链表存储,结点结构如下:
<table><tr><td>left</td><td>weight</td><td>right</td></tr></table>
<!-- page 130 -->
其中叶结点的weight域保存该结点的非负权值。设root为指向T的根结点的指针,请设计求T的WPL的算法,要求:
(1)给出算法的基本设计思想。
(2)使用C或C++语言,给出二叉树结点的数据类型定义。
(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
带权路径长度WPL有两种计算方法:
① WPL的定义:WPL=所有(叶结点的权值 Wi×该结点深度 Di)求和。
② 推导。如果该树的所有非叶结点满足以下条件:它的权值等于它的孩子结点权值之和。则WPL=所有非叶结点的权值和。 a n
因此本题有两种解法,都可以拿满分。
## 【解法1】前序/中序/后序遍历,下面给出的是先序遍历
(1)先序遍历二叉树,设置全局变量WPL,每次p访问到叶子结点时p的权值与深度相乘(根结点深度 depth=0),再累加到 WPL 中。
(2) 类型定义:
typedef struct BTNode{
int weight;
struct BTNode *left, *right;
}BTNode;
(3) 算法如下:
int WPL=0; // WPL是全局变量
void PreOrder(BTNode *p, int depth) //当前结点p,深度depth
{
if (p==NULL)
return;
if (p->left==NULL && p->right==NULL) // p是叶结点
wPL+=depth*p->weight;
Preorder(p->left, depth+1); //递归调用左孩子
Preorder(p->right, depth+1); // 递归调用右孩子
}
## 【解法2】后序遍历
(1)后序遍历二叉树,设置全局变量WPL,如果访问到叶结点则把权值返回给父亲,如果是非叶结点则该结点权值等于左、右孩子的权值和,累加到WPL中,并将该权值返回给父亲。
(2) 类型定义:
typedef struct BTNode{
int weight;
struct BTNode *left, *right;
}BTNode;
<!-- page 131 -->
(3) 算法如下:
<!-- verified-by-hand -->
> [!success] 已结合原文第 131 页及 Markdown 上下文人工校对
> 递归调用函数名大小写与定义不一致;合并行虽合法但应恢复清晰的两条语句。
int WPL = 0;
int PostOrder(BTNode *p)
{
if (p == NULL) return 0;
int L = PostOrder(p->left);
int R = PostOrder(p->right);
if (p->left == NULL && p->right == NULL) return p->weight;
WPL += L + R;
return L + R;
}
## 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++语言描述算法,关键之处给出注释。
## 【解法】中序遍历,递归
(1)中序遍历二叉树,对于当前访问的非空结点p,则先输出“(”,然后递归调用左子树,输出p的权值,递归调用右子树,输出“)”,如果p是根或者叶结点则不需要输出“(”或“)”。
(2) 算法如下:
<!-- page 132 -->
void InOrder(BTree* p, BTree* root)
{
if (p==NULL) return; // p是空结点
if ((p->left!=NULL || p->right!=NULL) && (p!=root))
printf("(");
Inorder(p->left, root); //递归调用左子树
printf("%s", p->data); //输出操作数
Inorder(p->right, root); // 递归调用右子树
if ((p->left!=NULL || p->right!=NULL) && (p!=root))
} printf(")");
## 2022年41题
(13分)已知非空二叉树T的结点值均为正整数,采用顺序存储方式保存,数据结构定义如下:
typedef struct {
int SqBiTNode[MAX_SIZE];
int ElemNum;
} SqBiTree;
//MAX_SIZE为已定义常量
//保存二叉树结点值的数组
//实际占用的数组元素个数
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>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>-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++语言描述算法,关键之处给出注释。
## 【解法】中序遍历
(1)如果当前结点的数组下标为i则左孩子下标为 2i+1,右孩子下标为2i+2,对该树进行递归的中序遍历,pre保存当前访问结点的前驱结点的权值,如果SqBiTree[i]<pre则说明中序遍历得到的不是升序,即当前子树返回false,如果SqBiTree[i]>=pre 且左子树和右子树都确定是二叉排序树则返回 true。
(2) 算法如下:
int pre=0; //全局变量pre保存中序前驱的权值,初值0
bool InOrder(SqBiTree *T, int i)
{
/*指针bt指向二叉树的结构体,i是当前访问的下标*/
if (i>=T->ElemNum |I T->SqBiTNode[i]==-1) // 当前结点为空
return true;
if (Inorder(T, 2*i+1) != true) // 递归调用左子树
return false; // 左子树不符合定义
if (pre>T->SqBiTNode[i])
return false;
pre=T->SqBiTNode[i];
if (Inorder(T, 2*i+2) != true)
return false;
return true;
}
<!-- page 133 -->
//访问当前结点visit(p)
// 前驱大于当前结点,不符合定义
//更新pre
//递归调用右子树
// 右子树不符合定义
//根下标为i的子树符合定义
## 5图
## 5.1 图概述
## 5.1.1 图的基本概念
想象一下,我们把图看作一张城市地图。地图上的每个城市就是一个顶点(Vertex),连接城市之间的道路就是一条边(Edge)。
• 路径:是从一个顶点到另一个顶点所经过的一系列边的集合。你从家里(起点)出发,经过几条街道(边),最终到达学校(终点)。这一整段路程就是一条路径。
• 环:也叫回路,是一条特殊的路径,它的起点和终点是同一个顶点。
• 自环:是一条从某个顶点出发,又立刻回到自身的边。
• 重边:是指在两个相同的顶点之间,存在多于一条的边。
• 无向图:图中的边(道路)没有方向,是双向的。
• 有向图:图中的边(道路)有明确的方向,是单行道。
· 无权图:所有的边都没有“权重”或“成本”的概念。我们只关心两个顶点之间有没有路,不关心路有多长。在地铁线路图上,我们通常只关心从A站到B站需要经过几站,而不关心每一站之间的具体距离。
• 有权图:每一条边都被赋予了一个数值,称为权重。连接两个城市的道路不仅存在,还标有长度(公里),这就是边的权重。 20
• 连通图:在一个无向图中,任意两个顶点之间都存在路径。
• 强连通图:在一个有向图中,任意两个顶点之间都存在路径。
• 完全图:在一个无向图中,任意两个顶点之间都存在边。
• 有向完全图:在一个有向图中,任意两个顶点之间都存在边。
• 子图:从原图中选取一部分顶点和一部分边组成的图。好比从一张完整的全国地图中,单独把“长三角地区”的地图拎出来。 van
•极大连通子图:指在保持“连通”这个性质的前提下,规模最大的子图。你不能再从原图中添加任何顶点或边到这个子图里,否则它就不再是连通的了。就是一个连通分量。
连通分量:对于一个非连通的无向图,图中“抱团”的、内部任意互通的极大连通子图。
• 生成树:在一个连通图中,选取一部分边,刚好能连接起所有的顶点,并且不形成任何环。这个由“最少的边”构成的骨架,就是一棵生成树。
<!-- page 134 -->
若一个图中没有自环和重边,它被称为简单图,考试中的图默认简单图。
本章默认:图的顶点集合为V,顶点数V为n,边集合为E,边数E为e。