2026年41题(回忆版)
强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 2026年41题(回忆版)
(13分)假定二叉搜索树使用二叉链表存储,存储结构如下:
typedef struct BSTNode{
int data;
struct BSTNode *left,*right;
}BSTNode;
typedef BSTNode BTNode;
<!-- page 186 -->
给了一个数k,求二叉树节点值和k的差的绝对值的最小值。设计尽可能高效的算法searchX(BTtree root,int k)输出最小值并输出所有最小值节点的关键字值。(1)给出算法的基本设计思想。(2)根据设计思想,采用C/C++描述算法,关键之处给出注释。
<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>预计得分</td></tr><tr><td rowspan=1 colspan=1>遍历两次 一中序遍历</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>10〜11</td></tr><tr><td rowspan=1 colspan=1>查找两次 一分别找 floor 和 ceil</td><td rowspan=1 colspan=1>O(n)水</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>12</td></tr><tr><td rowspan=1 colspan=1>查找一次- -同时维护 floor 和 ceil</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table>
## 【解法1】遍历两次—中序遍历
(1)对 BST 进行中序遍历得到升序序列。第一遍遍历计算每个结点与k的差值绝对值,记录全局最小差值 min\_diff。第二遍遍历输出 min\_diff 以及所有 |data - k| == min\_diff 的结点关键字。(2)算法如下:
<!-- verified-by-hand -->
> [!success] 已结合原文第 186 页及 Markdown 上下文人工校对
> 标识符中的下划线丢失、O 被识别成 0,且 InOrder2 和 searchX 被错误嵌套在前一个函数中。
int min_diff = INT_MAX;
void InOrder1(BSTNode *t, int k)
{
if (t == NULL) return;
InOrder1(t->left, k);
int diff = abs(t->data - k);
if (diff < min_diff) min_diff = diff;
InOrder1(t->right, k);
}
void InOrder2(BSTNode *t, int k)
{
if (t == NULL) return;
InOrder2(t->left, k);
if (abs(t->data - k) == min_diff) printf("%d ", t->data);
InOrder2(t->right, k);
}
void searchX(BSTNode *root, int k)
{
min_diff = INT_MAX;
InOrder1(root, k);
printf("最小差值为 %d,结点为:", min_diff);
InOrder2(root, k);
}
<!-- page 187 -->
## 或用一个InOrder 函数配合全局变量区分遍历次数
<!-- verified-by-hand -->
> [!success] 已结合原文第 187 页及 Markdown 上下文人工校对
> min_diff 被拆开,else 后混入中文;右子树递归错误地只在第二遍执行,searchX 也被嵌套。
int min_diff = INT_MAX;
int round = 0;
void InOrder(BSTNode *t, int k)
{
if (t == NULL) return;
InOrder(t->left, k);
if (round == 1)
{
int diff = abs(t->data - k);
if (diff < min_diff) min_diff = diff;
}
else
{
if (abs(t->data - k) == min_diff) printf("%d ", t->data);
}
InOrder(t->right, k);
}
void searchX(BSTNode *root, int k)
{
min_diff = INT_MAX;
round = 1; InOrder(root, k);
printf("%d: ", min_diff);
round = 2; InOrder(root, k);
}
(3)时间复杂度:O(n),空间复杂度:O(n)
## 【解法2】查找两次——分别找 small 和 big
(1)BST 中离 最近的结点必为 比 k小的最大值(small)或比 k大的最小值(big)。分别沿搜索路径查找 small 和 big,各需一次查找。比较 |small->data - k 和 |big->data - k|,输出更小者及对应的关键字。(2)算法如下:
void searchX(BSTNode *root, int k)
{
BSTNode *small = NULL, *big = NULL;
// — 查找 small:比 k 小的最大值 —
BSTNode *p = root;
while (p != NULL)
{
if (p->data == k) H
{
printf("0: %d", p->data);
return;
}
if (p->data < k) // 当前值更小,向右
{
small = p; // 记录 small 候选
p = p->right;
}
if (p->data > k) // 当前值更大,向左走
p = p->left;
}
// — 查找 big:比 k 大的最小值 —
p = root;
while (p != NULL)
{
//命中
{
printf("0: %d", p->data);
return;
}X
if (p->data > k) //当前值更大,向左走
big = p; // 记录 big 候选
p = p->left;
}
if (p->data < k)
p = p->right;
}
// — 比较 small 和 big,输出结果
int d_small, d_big;
if (small != NULL)
d_small = k - small->data;
else
d_small = INT_MAX;
if (big != NULL)
else
d_big = INT_MAX;
if (d_small < d_big) //左边更接近
printf("%d: %d", d_small, small->data);
if (d_big < d_small) // 右边更接近
printf("%d: %d", d_big, big->data);
if (d_small == d_big) //两边一样近
printf("%d: %d %d", d_small, small->data, big->data);
25
<!-- page 188 -->
(3) 时间复杂度:O(h)或O(n),空间复杂度:O(1)
【解法3】查找一次——同时维护 small 和 big
<!-- page 189 -->
(1)将两次查找合并为一次:从根向叶查找 k,沿途向右走时将当前结点记为small,向左走时记为big。走到底后比较两者与k的差值即可。只需一次遍历路径。(2)算法如下:
void searchX(BSTNode *root, int k)
{
BSTNode *small = NULL, *big = NULL, *p = root;
// — 一次查找,同时维护 small 和 big
while (p != NULL)
{
if (p->data == k) //精确命中
{
printf("0, %d", k);
return;
}
// 当前值 < k,向右
{
small= p; // 更新 small
p = p->right;
}
if (p->data > k) //当前值 > k,向左
big = p; // 更新 big
p = p->left;
}
// — 比较 small 和 big,输出结果 —
int d_small, d_big;
if (small != NULL)
d_small = k - small->data;
else
d small = INT MAX;
if (big != NULL)
d_big = big->data - k;
else
d_big = INT_MAX;
if (d_small < d_big) // 左边更接近
printf("%d, %d", d_small, small->data);
if (d_big< d_small) //右边更接近
printf("%d, %d", d_big, big->data);
if (d_small == d_big) //两边一样近
printf("%d, %d %d", d_small, small->data, big->data);
}
(3)时间复杂度:O(h)或O(n),空间复杂度:O(1)
<!-- page 190 -->
## 5图
## 5.1 图概述
## 5.1.1 图的基本概念
想象一下,我们把图看作一张城市地图。地图上的每个城市就是一个顶点(Vertex),连接城市之间的道路就是一条边(Edge)。
路径:是从一个顶点到另一个顶点所经过的一系列边的集合。你从家里(起点)出发,经过几条街道(边),最终到达学校(终点)。这一整段路程就是一条路径。
环:也叫回路,是一条特殊的路径,它的起点和终点是同一个顶点。
自环:是一条从某个顶点出发,又立刻回到自身的边。
重边:是指在两个相同的顶点之间,存在多于一条的边。
无向图:图中的边(道路)没有方向,是双向的。
有向图:图中的边(道路)有明确的方向,是单行道。
无权图:所有的边都没有“权重”或“成本”的概念。我们只关心两个顶点之间有没有路,不关心路有多长。在地铁线路图上,我们通常只关心从A站到B站需要经过几站,而不关心每一站之间的具体距离。 机
有权图:每一条边都被赋予了一个数值,称为权重。连接两个城市的道路不仅存在,还标有长度(公里),这就是边的权重。 X
连通图:在一个无向图中,任意两个顶点之间都存在路径。
强连通图:在一个有向图中,任意两个顶点之间都存在路径。
完全图:在一个无向图中,任意两个顶点之间都存在边。
有向完全图:在一个有向图中,任意两个顶点之间都存在边。
子图:从原图中选取一部分顶点和一部分边组成的图。好比从一张完整的全国地图中,单独把“长三角地区”的地图拎出来。
极大连通子图:指在保持“连通”这个性质的前提下,规模最大的子图。你不能再从原图中添加任何顶点或边到这个子图里,否则它就不再是连通的了。就是一个连通分量。
连通分量:对于一个非连通的无向图,图中“抱团”的、内部任意互通的极大连通子图。
生成树:在一个连通图中,选取一部分边,刚好能连接起所有的顶点,并且不形成任何环。这个由“最少的边”构成的骨架,就是一棵生成树。
若一个图中没有自环和重边,它被称为简单图,考试中的图默认简单图。
本章默认:图的顶点集合为 V,顶点数|V|为 n,边集合为 E,边数|E|为 e。
## 5.1.2 图的做题方法
<!-- page 191 -->
图的题是所有题目里面最简单的,重点复习遍历,其次可以复习拓扑排序,其他的图算法考察概率比较小。对于这些算法,建议以理解实现思路为主,知道对应算法由几步构成,每一步实现什么功能。在题目中如果遇到,可以先在草稿纸上列出流程,理清思路后再直接在试卷上分块作答:
原资料配图 · 转写内容请回源核验
<!-- page 192 -->
## 5.2 图的结构和遍历
在处理图的问题时,首先要确定其存储结构。考试中主要考察两种:邻接矩阵和邻接表。邻接表可以进一步演化为适用于无向图的邻接多重表和适用于有向图的十字链表。
<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>邻接多重表</td><td rowspan=1 colspan=2>十字链表</td></tr><tr><td rowspan=1 colspan=1>有向图</td><td rowspan=1 colspan=1>V</td><td rowspan=1 colspan=1>√</td><td rowspan=1 colspan=1>×</td><td rowspan=1 colspan=2>マ</td></tr><tr><td rowspan=1 colspan=1>无向图</td><td rowspan=1 colspan=1>√</td><td rowspan=1 colspan=1>√</td><td rowspan=1 colspan=1>√</td><td rowspan=1 colspan=1>X</td><td rowspan=1 colspan=1></td></tr></table>
## 5.2.1 邻接矩阵
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
有向图G1
无向图G2
邻接矩阵结构:
#define MAXV 100 // 最大顶点数
typedef struct // 图的定义
{
int numVertices,numEdges; //图中实际的顶点数和边数
char VerticesList[MAXV]; // 可选:每个顶点名称
int Edge[MAXV] [MAXV]; //邻接矩阵
}MGraph;
邻接矩阵通常使用0表示不存在边,Edge[i] i] =0 说明i到j之间不存在边。
无权图使用1表示存在边,有权图使用非0的数表示存在边。
邻接矩阵的遍历:
void TraverseEdges_MGraph(MGraph G)
{
for (int i = 0; i < G.numVertices; i++)
for (int j = 0; j < G.numVertices; j++)
if (G.Edge[i][j] != 0)
visit(i, j); // 如果i→j存在边则处理
}
<!-- page 193 -->
## 5.2.2 邻接表
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
原资料配图 · 转写内容请回源核验
## 邻接表结构:
#define MAXV 100 机考 // 最大顶点数
//边结点
typedef struct ArcNode
{
int adjvex; //邻接点在顶点表中的下标
struct ArcNode *next; //指向下一条边
int weight; //有权图可选:边权值
} ArcNode;
// 顶点结点
typedef struct VNode
{
char data; // 可选:顶点信息
ArcNode *first; //指向第一条邻接边
} VNode;
//邻接表图定义
typedef struct
{
VNode VerticesList[MAXV];// 顶点表
int numVertices,numEdges; // 顶点数、边数
}ALGraph;
## 图的结构体
请大家思考一个问题:顶点 VNode 定义中,如果我们不需要用到顶点信息,是不是可以省略这部分定义,从而整个邻接表结构可以写的更简单,该写法可能不太常用,但大家如果看到需要认识(习题中有出现):
typedef struct ArcNode
{
int adjvex;
struct ArcNode *next; // 指向下一条弧的指针
int weight; //有权图可选:边权值
}ArcNode; 土考 //边结点
typedef struct
ArcNode *VerticesList[MAXV]; //顶点数组
<!-- page 194 -->
<!-- verified-by-hand -->
> [!success] 已结合原文第 194 页及 Markdown 上下文人工校对
> 邻接表图定义被分页拆开;本块缺少承接顶点数组的结构体左花括号。
typedef struct
{
VNode VerticesList[MAXV];
int numVertices, arcnum;
} ALGraph;
## 邻接表的遍历:
void TraverseEdges_ALGraph(ALGraph G)
{
//遍历所有顶点
for (int i = 0; i < G.numVertices; i++)
//遍历当前顶点i发出的所有边
ArcNode *p = G.VerticesList[i].first;
while (p != NULL)
{
visit(p); //访问对应边
p = p->next; //处理下一条边
}
}
<!-- page 195 -->