步骤:

强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 步骤:

1. 初始化。初始化距离数组 dist,dist[s]= 0,所有其他顶点的距离为无穷大∞。同时,初始化路径数组 path 用于记录前驱结点。

<!-- page 207 -->

2. 循环直到所有顶点都已确定:a. 每次选择距离dist最小的、未选择过已确定顶点u。b.将顶点u视为已确定的顶点,之后不再考虑。c. 将u出发的所有邻接点ν做松弛:

if (dist[u] + w(u,v) &lt; dist[v])
dist[v] = dist[u] + w(u,v);
邻接矩阵代码如下,时间复杂度为 O(n²):
int Dijkstra(MGraph G, int start)
{
    int dist[MAXV], path[MAXV];
    // 1. 初始化
    bool visited[MAXV] = {false}; // 记录顶点是否已确定
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        dist[i] = INT_MAX; path[i] = -1;
    }
    //将起点到自身的距离设为0
    dist[start] = 0;
    //2.循环G.numVertices 次,每次找到一个最短路径并更新其他顶点
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        //a.在未访问的顶点中,找到距离起点最近的顶点u
        // 在第一次循环中,由于 dist[start] = 0,u 会被自然地赋值为 start
        int u = -1;
        int min_dist = INT_MAX;
        {
            if (visited[j] == false &amp;&amp; dist[j] &lt; min_dist)
            {
                min_dist = dist[j];
                u = j;
            }
        }
        // 如果找不到下一个顶点说明是非连通图
        // b标记 u 为已访问
        visited[u] = true;
        // c. 松弛操作:更新从u出发能够到达的所有未访问顶点的距离
        for (int v = 0; v &lt; G.numVertices; v++)
        {
            //条件:V未被访问过,目存在从 u到V的边
            // 并且通过u到达V的路径比当前记录的路径更短
            if (!visited[v] &amp;&amp; G.Edge[u][v] != 0 &amp;&amp; dist[u] + G.Edge[u][v] &lt; dist[v])
            {
                // 更新V 的最短距离和前驱结点
                dist[v] = dist[u] + G.Edge[u][v];
                path[v] = u;
            }
        }
    }
    根据 dist 数组输出长度,根据 path 数组输出路径
    return 0;
}
<!-- page 208 --> 请大家思考一个问题,Dijsktra 算法中的 visited 数组能否删去?为什么?邻接表代码如下,时间复杂度为 O(n²): 首计 <!-- verified-by-hand --> > [!success] 已结合原文第 208 页及 Markdown 上下文人工校对 > 循环变量、for 头和 min_dist 被 OCR 破坏,选点和松弛错误嵌进扫描循环;缺少 u==-1 检查会在非连通图访问下标 -1。
void Dijkstra_AL(ALGraph G, int start)
{
    int dist[MAXV], path[MAXV];
    bool visited[MAXV] = {false};
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        dist[i] = INT_MAX;
        path[i] = -1;
    }
    dist[start] = 0;
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        int min_dist = INT_MAX, u = -1;
        for (int j = 0; j &lt; G.numVertices; j++)
            if (!visited[j] &amp;&amp; dist[j] &lt; min_dist)
            {
                min_dist = dist[j];
                u = j;
            }
        if (u == -1) break;
        visited[u] = true;
        for (ArcNode *p = G.VerticesList[u].first; p != NULL; p = p-&gt;next)
        {
            int v = p-&gt;adjvex;
            if (!visited[v] &amp;&amp; dist[u] &lt;= INT_MAX - p-&gt;weight &amp;&amp; dist[u] + p-&gt;weight &lt; dist[v])
            {
                dist[v] = dist[u] + p-&gt;weight;
                path[v] = u;
            }
        }
    }
}
<!-- page 209 --> <table><tr><td>return 0; 15</td></tr></table> 对于邻接表来说,时间复杂度主要瓶颈在于选择距离最短的点,这一步可以使用优先队列优化 <!-- page 210 --> ## 5.3.4 最小生成树 给定一个连通无向图 G=(V, E),其生成树是: 一个包含图中所有 n 个顶点、n-1 条边的无环子图。 每个顶点都能通过生成树中的边互相连通。 在所有可能的生成树中,边的权值之和最小的那一棵生成树,称为最小生成树。 ## Prim算法 核心思想:从顶点的角度出发,初始时已选顶点集合只含起始顶点。每次将一个顶点纳入已选顶点集合,重复n-1 次直到该集合包含所有顶点。 L ## 步骤: 1. 初始化。从源点出发,标记该顶点已选。 2. 重复执行 n-1 次:a. 每次从未选顶点中找出距离已选顶点集合最近的顶点 k,将该顶点标
原资料配图
原资料配图 · 转写内容请回源核验
(f)
原资料配图 · 转写内容请回源核验

<!-- page 211 -->

原资料配图
原资料配图 · 转写内容请回源核验
(g)
原资料配图 · 转写内容请回源核验
(h) (i)
原资料配图 · 转写内容请回源核验

## 邻接矩阵

## 伪代码如下:

<!-- verified-by-hand -->
> [!success] 已结合原文第 211 页及 Markdown 上下文人工校对
> 最小顶点选择时无条件覆盖 min 和 k,更新时没有排除用 0 表示的无边;作用域、赋值符和输出语句也被破坏。
void Prim(MGraph G)
{
    int cost[MAXV], parent[MAXV];
    bool visited[MAXV] = {false};
    int start = 0;
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        if (G.Edge[start][i] != 0)
        {
            cost[i] = G.Edge[start][i];
            parent[i] = start;
        }
        else
        {
            cost[i] = INT_MAX;
            parent[i] = -1;
        }
    }
    cost[start] = 0;
    visited[start] = true;
    for (int i = 1; i &lt; G.numVertices; i++)
    {
        int min_cost = INT_MAX, k = -1;
        for (int j = 0; j &lt; G.numVertices; j++)
            if (!visited[j] &amp;&amp; cost[j] &lt; min_cost)
            {
                min_cost = cost[j];
                k = j;
            }
        if (k == -1) break;
        visited[k] = true;
        output_edge(parent[k], k);
        for (int j = 0; j &lt; G.numVertices; j++)
            if (!visited[j] &amp;&amp; G.Edge[k][j] != 0 &amp;&amp; G.Edge[k][j] &lt; cost[j])
            {
                cost[j] = G.Edge[k][j];
                parent[j] = k;
            }
    }
}
<!-- page 212 --> ## 时间复杂度:O(n²) ## Prim 算法和 Dijsktra 算法的比较 Prim 每次从未选顶点中选择距离已选集合最近的点(cost 最小),而 Dijsktra 算法每次从未选顶点中选择距离源点最近的顶点(dist 最小)。 Prim 的 cost 记录的是边的长度,Dijsktra 的 dist 记录的是路径的长度。 Prim 中 cost[k] 的更新条件是 k 距离本次选择的顶点j 比距离已选集合中的其他顶点更近:cost[j] > w(k, j)。 Dijsktra 中 dist[v] 的更新条件是借道 u 之后源点离v 的距离比借道 u 之前更小:dist[u] + w(u, v) < dist[v]。 Prim 和 Dijsktra 都是贪心算法每次选择 xx 最小的,邻接表的写法都可以使用小根堆来优化选择的过程。 <!-- page 213 --> ## Kruskal算法 核心思想:从边的角度出发,初始时已选边集合不包含任何边。每次将一条最小的、加入集合后不成环的边纳入已选边集合,重复n-1 次。 尊 ## 步骤: 1. 题目给出从小到大排序的边数组或给出优先队列(堆)。 2. 初始化并查集,初始时所有顶点属于不同集合。 3. 循环,直到选出 n-1 条不成环的边:a. 对于还未处理的最小边,使用并查集判断该边的两个端点分别属于哪个集合。b. 如果不属于同一个集合则选择该边、合并这两个集合。 ## 邻接表 伪代码如下(忽略快速排序):
int parent[MAXV];
//边结构体定义
typedef struct {
    int u, v; // 边的两个顶点
    int weight; //边的权值
} Edge;
//查找根结点 int Find(int x)
if (parent[x] != x)
parent[x] = Find(parent[x]); //路径压缩
return parent[x];
}
//并查集:合并集合
{
    parent[Find(y)] = Find(x);
}
void Kruskal(ALGraph G)
{
    Edge edges[MAXE]; //边集数组
    int count = 0; //统计已纳入生成树中的边数
    //1.题目给出从小到大排序的边数组或给出优先队列(堆)
    不妨假设所有的边已经在数组 edges 中,边的数量为m
    Qsort(edges[], 0, G.numEdges-1);
    //2.初始化并查集,初始时所有顶点属于不同集合
    parent[i] = i;
<!-- page 214 --> <!-- code-audit: false_positive --> > [!info] 已人工审读:这是从第 213 页延续的 Kruskal 跨页下半段,单独代码块的花括号不平衡属于分页误报;上半段的 OCR 问题应作为整段另行校对。
for (int i = 0; i &lt; m; i++)
{
    //3-a.对于还未处理的最小边,使用并查集判断该边的两个端点分别属于哪个集合
    int rootU = Find(edges[i].u);
    int rootV = Find(edges[i].v);
    if (rootU != rootV)
    {
        Union(rootU, rootV);
        将该边加入到生成树中
        count++;
        if (count == G.numVertices-1) break; // 已选择 n-1 条边
    }
}
输出最小生成树 机考研
}
第一步在考试中不太可能考察,主要考察第二、三步。 <!-- page 215 --> ## 5.3.5 拓扑排序
原资料配图
原资料配图 · 转写内容请回源核验

## 核心目的:

解决有向无环图(DAG)中顶点的先后顺序问题,得到拓扑序列。

如果顶点 a 到顶点 b 存在有向边,则在拓扑序列中,顶点 a 会在顶点b 前面。

如果该有向图无环,则拓扑序列存在。如果该有向图有环,则拓扑序列不存在。

<table><tr><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>每次选择结点时都只有一个结点可选</td></tr><tr><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>图中有环,环中的顶点未被访问</td></tr></table>

## 步骤:

1. 统计每个顶点的入度。

2. 将入度为0 的顶点入队。

3. 队列出队一个顶点,访问它,并将它的出边所指向顶点的入度减1。

4. 若减到0,再次入队。

5. 队列为空,排序完成;若有未访问顶点,存在环。

## 邻接表

时间复杂度:O(n+e)

代码如下:

<!-- page 216 -->

void TopoSort(ALGraph G)
{
    int indegree[MAXV] = {0};
    //计算每个顶点的入度
    for (int i = 0; i &lt; G.numVertices; i++)
    {
        while (p!=NULL)
        {
            indegree[p-&gt;adjvex]++;
            p = p-&gt;next;
        }
    }
    int queue[MAXV], front = 0, rear = 0;
    for (int i = 0; i &lt; G.numVertices; i++)
    if (indegree[i] == 0)
    queue[rear++] = i;
    while (front &lt; rear)
    {
        int u = queue[front++];
        输出顶点u
        //此处可判断是否只有一个顶点可选,即front+1==rear
        for (ArcNode *p = G.VerticesList[u].first; p != NULL; p = p-&gt;next)
        {
            int v = p-&gt;adjvex;
            if (--indegree[v] == 0)
            queue[rear++] = v;
        }
    }
    if (rear &lt; G.numVertices)
    拓扑序列不存在,该有向图存在环
}
## \*邻接矩阵 时间复杂度:O(n²) 代码如下:
void TopoSort(MGraph G)
{
    int indegree[MAXV] = {0};
    //计算每个顶点的入度
    for (int i = 0; i &lt; G.numVertices; i++)
    for (int j = 0; j &lt; G.numVertices; j++)
    if (G.Edge[i][j] != 0) //存在边 i→j
    indegree[j]++;
}
<!-- page 217 --> <!-- verified-by-hand --> > [!success] 已结合原文第 217 页及 Markdown 上下文人工校对 > 环判断被错误嵌入邻点循环且 while 的闭合花括号丢失;应在排序完成后按已输出顶点数判断。
int queue[MAXV], front = 0, rear = 0;
for (int i = 0; i &lt; G.numVertices; i++)
    if (indegree[i] == 0) queue[rear++] = i;
while (front &lt; rear)
{
    int u = queue[front++];
    output(u);
    for (int v = 0; v &lt; G.numVertices; v++)
        if (G.Edge[u][v] != 0 &amp;&amp; --indegree[v] == 0)
            queue[rear++] = v;
}
if (rear &lt; G.numVertices) report_cycle();
<!-- page 218 --> ## 5.3.6 \*关键路径
原资料配图
原资料配图 · 转写内容请回源核验

以边表示活动的网叫AOE网,它是带权无环图,顶点表示事件,弧表示活动,权表示活动持续的时间。通常,AOE-网可用来估算工程的完成时间。

在正常的情况(无环)下,网中只有一个入度为0的点,称作源点,也只有一个出度为0的点,称作汇点。在AOE-网中,一条路径各弧上的权值之和称为该路径的带权路径长度(后面简称路径长度)。要估算整项工程完成的最短时间,就是要找一条从源点到汇点的带权路径长度最长的路径,称为关键路径。

关键路径上的活动叫作关键活动,这些活动是影响工程进度的关键,它们的提前或延期将使整个工程提前或延期。

目的:关键路径用于工程调度、项目管理等任务中,找出一条耗时最长的必经路径,控制整个项目进度。