【数据结构】第6章 图

这是本人根据王道考研数据结构课程整理的笔记,希望对您有帮助。

6.1 图

图的定义

G G G顶点集 V V V边集 E E E 组成,记为 G = ( V , E ) G=(V,E) G=(V,E),其中 $ V(G)$ 表示图 G G G 中顶点的有限非空集; E ( G ) E(G) E(G) 表示图 G G G 中顶点之间的关系(边)集合。若 V = { v 1 , v 2 , … , v n } V=\{v_1,v_2,\dots,v_n\} V={v1,v2,,vn},则用 ∣ V ∣ \left| V \right| V 表示图 G G G顶点的个数,也称 G G G 的阶 E = { ( u , v ) ∣ u ∈ V , v ∈ V } E=\{(u, v) \mid u \in V, v \in V\} E={(u,v)uV,vV},用 ∣ E ∣ \left| E \right| E 表示图 G G G边的条数
在这里插入图片描述

无向图

E E E无向边(简称)的有限集合时,则图 G G G无向图。边是顶点的无序对,记为 ( v , w ) (v,w) (v,w) ( w , v ) (w,v) (w,v) ,因为 ( v , w ) = ( w , v ) (v,w)=(w,v) (v,w)=(w,v),其中 v , w v,w v,w 是顶点。可以说顶点 w w w 和顶点 v v v 互为邻接点。边 ( v , w ) (v,w) (v,w) 依附于顶点 w w w v v v,或者说边 ( v , w ) (v,w) (v,w) 和顶点 v , w v,w v,w 相关联。
在这里插入图片描述

G 1 = ( V 1 , E 1 ) V 1 = { A , B , C , D , E } E 1 = { ( A , B ) , ( B , D ) , ( B , E ) , ( C , D ) , ( C , E ) , ( D , E ) } G_1=(V_1,E_1)\\ V_1=\{A,B,C,D,E \} \\ E_1=\{(A,B),(B,D),(B,E),(C,D),(C,E),(D,E)\} G1=(V1,E1)V1={A,B,C,D,E}E1={(A,B),(B,D),(B,E),(C,D),(C,E),(D,E)}
有向图

E E E有向边(简称)的有限集合时,则图 G G G有向图。弧是顶点的有序对,记为 ⟨ v , w ⟩ \lang v,w \rang v,w ,其中 v , w v,w v,w 是顶点, v v v 称为弧尾 w w w称为弧头 ⟨ v , w ⟩ \langle v,w \rangle v,w称为从顶点 v v v 到顶点 w w w 的弧,也称 v v v 邻接到 w w w,或 w w w 邻接自 v v v。$ \lang v,w \rang\neq \lang w,v\rang $。
在这里插入图片描述

G 2 = ( V 2 , E 2 ) V 2 = { A , B , C , D , E } E 2 = { ⟨ A , B ⟩ , ⟨ A , C ⟩ , ⟨ A , D ⟩ , ⟨ A , E ⟩ , ⟨ B , A ⟩ , ⟨ B , C ⟩ , ⟨ B , E ⟩ , ⟨ C , D ⟩ } G_2=(V_2,E_2)\\ V_2=\{A,B,C,D,E \} \\ E_2=\{\lang A,B \rang,\lang A,C \rang,\lang A,D \rang,\lang A,E \rang,\lang B,A \rang,\lang B,C \rang,\lang B,E \rang,\lang C,D \rang\} G2=(V2,E2)V2={A,B,C,D,E}E2={A,B,A,C,A,D,A,E,B,A,B,C,B,E,C,D}
顶点的度、入度、出度

  • 对于无向图
    • 顶点 v v v 的度是指依附于该顶点的边的条数,记为 TD ( v ) \text{TD}(v) TD(v)
    • 在具有 n n n 个顶点、 e e e 条边的无向图中, ∑ i = 1 n TD ( v i ) = 2 e \sum_{i=1}^n{\text{TD}(v_i)}=2e i=1nTD(vi)=2e,即无向图的全部顶点的度的和等于边数的2倍
  • 对于有向图
    • 入度是以顶点 v v v终点的有向边的数目,记为 ID ( v ) \text{ID}(v) ID(v)
    • 出度是以顶点 v v v起点的有向边的数目,记为 OD ( v ) \text{OD}(v) OD(v)
    • 顶点 v v v 的度的等于其入度和出度之和,即 TD ( v ) = ID ( v ) + OD ( v ) \text{TD}(v) = \text{ID}(v)+\text{OD}(v) TD(v)=ID(v)+OD(v)
    • 在具有 n n n 个顶点、 e e e 条边的有向图中, ∑ i = 1 n ID ( v i ) = ∑ i = 1 n OD ( v i ) = e \sum_{i=1}^n{\text{ID}(v_i)}=\sum_{i=1}^n{\text{OD}(v_i)}=e i=1nID(vi)=i=1nOD(vi)=e

顶点-顶点的关系描述

路径:顶点 v p v_p vp 到顶点 v q v_q vq 之间的一条路径是指顶点序列, v p , v i 1 , v i 2 , … , v i m , v q v_p,v_{i_1},v_{i_2},\dots,v_{i_m},v_{q} vp,vi1,vi2,,vim,vq

回路:第一个顶点和最后一个顶点相同的路径称为回路或环( n n n 个顶点的图,若 ∣ E ∣ > n − 1 \left| E \right|>n-1 E>n1,则一定有回路)

简单路径:在路径序列中,顶点不重复出现的路径称为简单路径

简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路

路径长度:路径上边的数目

点到点的距离:从顶点 u u u 出发到顶点 v v v 的最短路径若存在,则此路径的长度称为从 u u u v v v 的距离。若从 u u u v v v 根本不存在路径,则记该距离为无穷( ∞ \infty )

无向图中,若从顶点 v v v 到顶点 w w w 有路径存在,则称 v v v w w w连通

有向图中,若从顶点 v v v 到顶点 w w w 和从顶点 w w w 到顶点 v v v 之间都有路径,则称这两个顶点是强连通的。

连通图、强连通图

连通图:(无向)图中任意两个顶点都是连通

强连通图:(有向)图中任意一对顶点都是强连通

子图、生成子图
在这里插入图片描述

生成子图:原图所有的顶点 + 部分边

连通分量

无向图极大连通子图(子图必须相连,且包含尽可能多的顶点和边)称为连通分量
在这里插入图片描述

有向图极大强连通子图(子图必须强连通,且包含尽可能多的顶点和边)称为强连通分量
在这里插入图片描述

生成树

连通图生成树包含图中全部顶点的一个极小连通子图(边要尽可能的少,但要保持连通)

若图中顶点数为 n n n,则它的生成树含有 n − 1 n-1 n1 条边。
在这里插入图片描述

生成森林

非连通图中,连通分量的生成树构成了非连通图的生成森林
在这里插入图片描述

边的权、带权图/网

边的权:在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值。

带权图/网:边上带有权值的图称为带权图,也称

带权路径长度:当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度。
在这里插入图片描述

完全图

无向完全图:无向图中任意两个顶点之间都存在边
在这里插入图片描述

若无向图的顶点数 ∣ V ∣ = n \left| V\right|=n V=n,则 ∣ E ∣ ∈ [ 0 , C n 2 ] = [ 0 , n ( n − 1 ) / 2 ] |E| \in\left[0, C_{n}^{2}\right]=[0, n(n-1) / 2] E[0,Cn2]=[0,n(n1)/2]

有向完全图:有向图中任意两个顶点之间都存在方向相反的两条弧
在这里插入图片描述

若有向图的顶点数 ∣ V ∣ = n \left| V\right|=n V=n,则 ∣ E ∣ ∈ [ 0 , 2 C n 2 ] = [ 0 , n ( n − 1 ) ] |E| \in\left[0, 2C_{n}^{2}\right]=[0, n(n-1)] E[0,2Cn2]=[0,n(n1)]

稀疏/稠密图

稀疏图边数很少的图

稠密图边数很多的图

不存在回路,且连通无向图 n n n 个顶点的树,必有 n − 1 n-1 n1 条边)

有向树:一个顶点的入度为0,其余顶点的入度均为1的有向图,称为有向树。
在这里插入图片描述

6.2 图的存储及基本操作

6.2.1 邻接矩阵法

邻接矩阵法
在这里插入图片描述

#define MaxVertexNum 100	//顶点数目的最大值
typedef struct
{
    char Vex[MaxVertexNum];	//顶点表
    int/bool Edge[MaxVertexNum][MaxVertexNum];	//邻接矩阵,边表
    int vexnum,arcnum;		//图的当前顶点数和边数/弧数
}MGraph;

邻接矩阵法存储带权图
在这里插入图片描述

有的时候会把自己指向自己的边的权值设为0:
在这里插入图片描述

#include <climits>
#define MaxVertexNum 100	//顶点数目的最大值
#define INFINITY INT_MAX	//宏定义常量“无穷”
typedef struct
{
    char Vex[MaxVertexNum];	//顶点
    int Edge[MaxVertexNum][MaxVertexNum];	//边的权
    int vexnum,arcnum;		//图的当前顶点数和边数/弧数
}MGraph;

邻接矩阵法的性能分析

  • 空间复杂度: O ( ∣ V ∣ 2 ) O(\left| V\right|^2) O(V2)——只和顶点数相关,和实际的边数无关
  • 适合用于存储稠密图
  • 无向图的邻接矩阵是对称矩阵,可以压缩存储(只存储上三角区/下三角区)
    在这里插入图片描述

邻接矩阵法的性质

设图 G G G 的邻接矩阵为 A A A (矩阵元素为0/1),则 A n A^n An 的元素 A n [ i ] [ j ] A^n[i][j] An[i][j] 等于由顶点 i i i 到顶点 j j j 的长度为 n n n 的路径的数目。

6.2.2 邻接表法
#define MaxVertexNum 100	//顶点数目的最大值
typedef char VertexType;	//定义顶点类型
//【边/弧】
typedef struct ArcNode
{
    int adjvex;				//边/弧指向哪个结点
    struct ArcNode *next;	//指向下一条弧的指针
    //InfoType info;		//边权值
}ArcNode;
//【顶点】
typedef struct VNode
{
    VertexType data;	//顶点信息
    ArcNode *first;		//第一条边/弧
}VNode, AdjList[MaxVertexNum];
//用邻接表存储的图
typedef struct
{
    AdjList vertices;
    int vexnum, arcnum;
}ALGraph;

在这里插入图片描述

邻接表的性质
在这里插入图片描述

6.2.3 十字链表法(有向图)

在这里插入图片描述

缺点:删除操作不方便

6.2.4 邻接多重表(无向图)

在这里插入图片描述


总结:
在这里插入图片描述

6.2.5 图的基本操作

Adjacent(G,x,y):判断图 G G G 是否存在边 ⟨ x , y ⟩ \lang x,y\rang x,y ( x , y ) (x,y) (x,y)

Neighbors(G,x):列出图 G G G 中与结点 x x x 邻接的边。

InsertVertex(G,x):在图 G G G 中插入顶点 x x x

DeleteVertex(G,x):从图 G G G 中删除顶点 x x x

AddEdge(G,x,y):若无向边 ( x , y ) (x,y) (x,y) 或有向边 ⟨ x , y ⟩ \lang x,y \rang x,y 不存在,则向图 G G G 中添加该边。

FirstNeighbor(G,x):求图 G G G 中顶点 x x x 的第一个邻接点,若有则返回顶点号。若 x x x 没有邻接点或图中不存在 x x x,则返回-1。

NextNeighbour(G,x,y):假设图 G G G 中顶点 y y y 是顶点 x x x 的一个邻接点,返回除 y y y 之外顶点 x x x 的下一个邻接点的顶点号,若 y y y x x x 的最后一个邻接点,则返回-1。

Get_edge_value(G,x,y):获取图 G G G 中边 ( x , y ) (x,y) (x,y) ⟨ x , y ⟩ \lang x,y\rang x,y 对应的权值。

Set_edge_value(G,x,y):设置图 G G G 中边 ( x , y ) (x,y) (x,y) ⟨ x , y ⟩ \lang x,y\rang x,y 对应的权值为 v v v

Adjacent(G,x,y):判断图 G G G 是否存在边 ⟨ x , y ⟩ \lang x,y\rang x,y ( x , y ) (x,y) (x,y)

6.3 图的遍历

6.3.1 广度优先遍历(BFS)

要点:

  1. 找到与一个顶点相邻的所有顶点
    • FirstNeighbor(G,x):求图 G G G 中顶点 x x x 的第一个邻接点,若有则返回顶点号。若 x x x 没有邻接点或图中不存在 x x x,则返回-1。
    • NextNeighbour(G,x,y):假设图 G G G 中顶点 y y y 是顶点 x x x 的一个邻接点,返回除 y y y 之外顶点 x x x 的下一个邻接点的顶点号,若 y y y x x x 的最后一个邻接点,则返回-1。
  2. 标记哪些顶点被访问过
    • bool visited[MAX_VERTEX_NUM]; //访问标记数组
  3. 需要一个辅助队列

bool visited[MAX_VERTEX_NUM];	//访问标记数组,初始都为false
//加入了对非连通图的出咯
void BFSTraverse(Graph G)	//对图G进行广度优先遍历
{
    for(i = 0; i < G.vexnum; ++i)
		visited[i] = FALSE;	//访问标记数组初始化
    InitQueue(Q);			//初始化辅助序列Q
    for(i = 0; i < G.vexnuml ++i)	//从0号顶点开始遍历
    {
        if(!visited[i])		//对每个连通分量调用一次BFS
            BFS(G, i);		//vi未访问过,从vi开始BFS
    }
}
//广度优先遍历
void BFS(Graph G, int v)	//从顶点v出发,广度优先遍历图G
{
    visit(v);				//访问初始顶点v
    visited[v] = TRUE;		//对v做已访问标记
    EnQueue(Q, v);			//顶点v入队列Q
    while(!isEmpty(Q))
    {
        DeQueue(Q, v);		//顶点v出队列
        for(w = FirstNeighbor(G, v); w >= 0; w = NextNeighbour(G, v, w))	//检测v所有邻接点
        {
            if(!visited[w])	//w为v的尚未访问的邻接顶点
            {
                visit(w);	//访问顶点w
                visited[w] = TRUE;	//对w做已访问标记
                EnQueue(Q, w);	//顶点w入队列
            }
        }
    }
}

广度优先生成树
在这里插入图片描述

广度优先生成森林
在这里插入图片描述

6.3.2 深度优先遍历(DFS)
bool visited[MAX_VERTEX_NUM];	//访问标记数组
void DFSTraverse(Graph G)	//对图G进行深度优先遍历
{
    for(v = 0; v < G.vexnum; ++v)
        visited[v] = FALSE;	//初始化已访问标记数据
    for(v = 0; v < G.vexnum; ++v)	//本代码是从v=0开始遍历
    {
        if(!visited[v])
            DFS(G, v);
    }
}
void DFS(Graph G, int v)	//从顶点v出发,深度优先遍历图G
{
    visit(v);	//访问顶点v
    visited[v] = TRUE;	//设已访问标记
    for(w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
    {
        if(!visited[w])	//w为u的尚未访问的邻接顶点
        {
            DFS(G, w);
        }
    }
}

深度优先生成树
在这里插入图片描述

(如果沿着某条边找到了结点,就把这条边标红。把红色边留下,就得到了深度优先生成树)
在这里插入图片描述

深度优先生成森林
在这里插入图片描述
在这里插入图片描述

6.3.3图的遍历与图的连通性
  • 无向图进行BFS/DFS遍历,调用BFS/DFS函数的次数=连通分量数
    • 对于连通图,只需调用1次BFS/DFS
  • 有向图进行BFS/DFS遍历,调用BFS/DFS函数的次数要具体问题具体分析
    • 若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数
    • 对于强连通图,从任一结点出发都只需调用1次BFS/DFS

6.4 图的应用

6.4.1 最小生成树

对于一个带权连通无向图 G = ( V , E ) G=(V,E) G=(V,E),生成树不同,每棵树的权(即树中所有边上的权值之和)也可能不同。设 R R R G G G 的所有生成树的集合,若 T T T R R R边的权值之和最小的生成树,则 T T T 称为 G G G最小生成树(Minimum-Spanning-Tree, MST)

  • 最小生成树可能有多个,但边的权值之和总是唯一且最小的
  • 最小生成树的边数 = 顶点数 - 1。砍掉一条则不连通,增加一条边则会出现回路
  • 如果一个连通图本身就是一棵树,则其最小生成树就是它本身
  • 只有连通图才有生成树,非连通图只有生成森林

Prim算法(普里姆)

从某一个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。
在这里插入图片描述
在这里插入图片描述

时间复杂度: O ( ∣ V ∣ 2 ) O(\left|V \right|^2) O(V2),适合用于边稠密

Kruskal算法(克鲁斯卡尔)

每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直至所有结点都连通。
在这里插入图片描述

时间复杂度: O ( ∣ E ∣ log ⁡ 2 ∣ E ∣ ) O(\left|E \right| \log_2\left|E \right|) O(Elog2E),适合用于边稀疏

6.4.2 最短路径问题(BFS算法)

在这里插入图片描述

//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(Graph G, int u)
{
    //d[i]表示从u到i结点的最短路径
    for(i = 0; i < G.vexnum; ++i)
    {
        d[i] =;		//初始化路径长度
        path[i] = -1;	//最短路径从哪个顶点过来
    }
    d[u] = 0;
    visited[u] = TRUE;
    EnQueue(Q, u);
    while(!isEmpty(Q))	//BFS算法主过程
    {
        DeQueue(Q, u);	//队头元素u出队
        for(w = FirstNeighbor(G, u); w >= 0; w = NextNeighbor(G, u, w))
        {
            if(!visited[w])		//w为u的尚未访问的邻接顶点
            {
                d[w] = d[u] + 1;	//路径长度加1
                path[w] = u;		//最短路径应从u到w
                visited[w] = TRUE;	//设已访问标记
                EnQueue(Q, w);		//顶点w入队
            }
        }
    }
}

在这里插入图片描述

总结:

  • 就是对BFS的小修改,再visit一个顶点时,修改其最短路径长度d[]并在path[]记录前驱结点。
  • BFS算法求单源最短路径只适用于无权图,或所有边的权值都相同的图
6.4.3 最短路径问题(Dijkstra算法)

在这里插入图片描述

回顾:

带权路径长度:当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度。

以下将带权路径长度简称为路径长度

初始:从V0开始,初始化三个数组信息如下:
在这里插入图片描述

第1轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi,令final[i]=true。检查所有邻接自Vi的顶点,若其final值为false,则更新dist和path信息。
在这里插入图片描述

第2轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi,令final[i]=true。检查所有邻接自Vi的顶点,若其final值为false,则更新dist和path信息。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

结论:Dijkstra算法不适用于有负权值的带权图。

6.4.4 最短路径算法(Floyd算法)

在这里插入图片描述

初始:不允许再其他顶点中转,最优路径是?
在这里插入图片描述

若允许在V0中转,最短路径是?——求A(0)和path(0)
在这里插入图片描述

若允许在V0、V1中转,最短路径是?——求A(1)和path(1)
在这里插入图片描述

若允许在V0、V1、V2中转,最短路径是?——求A(2)和path(2)
在这里插入图片描述

总结:从A(-1)和path(-1)开始,经过 n n n 轮递推,得到A(n-1)和path(n-1)

//......准备工作,根据图的信息初始化矩阵A和path
for(int k = 0; k < n; k++)	//考虑以Vk作为中转点
{
    for(int i = 0; i < n; i++)	//遍历整个矩阵,i为行号,j为列号
    {
        for(int j = 0; j < n; j++)
        {
            if(A[i][j] > A[i][k] + A[k][j])	//以Vk为中转点的路径更短
            {
                A[i][j] = A[i][k] + A[k][j];	//更新最短路径长度
                path[i][j] = k;	//中转点
            }
        }
    }
}

结论:

Dijkstra算法适用于**有负权值但是没有负权回路(有负权值的边组成回路)**的的带权图。


总结:
在这里插入图片描述

6.4.5 有向无环图(DAG)

有向无环图:若一个有向图不存在环,则称为有向无环图,简称DAG图(Directed Acyclic Graph)
在这里插入图片描述

6.4.6 拓扑排序

AOV网

AOV网(Activity On Vertex Network,用顶点表示活动的网):用有向无环图表示一个工程。顶点表示获得,有向边 ⟨ V i , V j ⟩ \lang V_i,V_j\rang Vi,Vj 表示活动 V i V_i Vi 必须先于活动 V j V_j Vj 进行。
在这里插入图片描述

拓扑排序

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑序列:

  1. 每个顶点出现且只出现一次
  2. 若顶点 A A A 在序列中排在顶点 B B B 的前面,则在图中不存在从顶点 B B B 到顶点 A A A 的路径。

拓扑排序的实现:

  1. 从AOV网中选择一个没有前驱(入度为0)的顶点并输出
  2. 从网中删除该顶点和所有以它为起点的有向边
  3. 重复1和2直到当前的AOV网为空当前网中不存在无前驱的顶点为止
    在这里插入图片描述
#define MaxVertexNum 100	//图中顶点数目的最大值
#define VertexType xxx		//定义顶点类型
typedef struct ArcNode		//边表结点
{
    int adjvex;		//该弧所指向的顶点的位置
    struct ArcNode *nextarc;	//指向下一条弧的指针
    //InfoType info;	//网的边权值
}ArcNode;
typedef struct VNode	//顶点表结点
{
    VertexType data;	//顶点信息
    ArcNode *firstarc;	//指向第一条依附该顶点的弧的指针
}VNode, AdjList[MaxVertexNum];
typedef struct
{
    AdjList vertices;	//邻接表
    int vexnum, arcnum;	//图的顶点数和弧数
}Graph;	//Graph是以邻接表存储的图类型
bool TopologicalSort(Graph G)
{
    InitStack(S);	//初始化栈,存储入度为0的顶点
    for(int i = 0; i < G.vexnum; i++)
    {
        if(indegree[i] == 0)
            Push(S, i);	//将所有入度为0的顶点进栈
    }
    int count = 0;		//计数,记录当前已经输出的顶点数
    while(!IsEmpty(S))	//栈不空,则存在入度为0的顶点
    {
        Pop(S, i);		//栈顶元素出栈
        print[count++] = i;	//输出顶点i
        for(p = G.vertices[i].firstarc; p; p = p->nextarc)
        {
            //将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
            v = p->adjvex;
            if(!(--indegree[v]))
                Push(S, v);	//入度为0,则入栈
        }
    }
    if(count < G.vexnum)
        return false;	//排序失败,有向图中有回路
    else
        return true;	//拓扑排序成功
}

逆拓扑排序

逆拓扑排序的实现:

  1. 从AOV网中选择一个没有后继(出度为0)的顶点并输出
  2. 从网中删除该顶点和所有以它为终点的有向边
  3. 重复1和2直到当前的AOV网为空为止
    在这里插入图片描述

用DFS算法实现逆拓扑排序(区别是只在DFS函数中加了一个print):

bool visited[MAX_VERTEX_NUM];	//访问标记数组
void DFSTraverse(Graph G)	//对图G进行深度优先遍历
{
    for(v = 0; v < G.vexnum; ++v)
        visited[v] = FALSE;	//初始化已访问标记数据
    for(v = 0; v < G.vexnum; ++v)	//本代码是从v=0开始遍历
    {
        if(!visited[v])
            DFS(G, v);
    }
}
void DFS(Graph G, int v)	//从顶点v出发,深度优先遍历图G
{
    visit(v);	//访问顶点v
    visited[v] = TRUE;	//设已访问标记
    for(w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
    {
        if(!visited[w])	//w为u的尚未访问的邻接顶点
        {
            DFS(G, w);
        }
    }
    print(v);		//输出顶点
}
6.4.7 关键路径

AOE网

在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网(Activity On Edge Network)

  • AOE网仅有一个入度为0的点,称为开始顶点(源点),它表示整个工程的开始
  • AOE网仅有一个出度为0的点,称为结束顶点(汇点),它表示整个工程的结束

性质:

  • 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
  • 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
  • 有些活动是可以并行进行的
    在这里插入图片描述

关键路径

从源点到汇点的有向路径可能有多条,所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动
在这里插入图片描述
在这里插入图片描述

活动 a i a_i ai时间余量 d ( i ) = l ( i ) − e ( i ) d(i)=l(i)-e(i) d(i)=l(i)e(i),表示在不增加完成整个工程所需总时间的情况下,活动 a i a_i ai 可以拖延的时间。若一个活动的时间余量为0,则说明该活动必须要如期完成, d ( i ) = 0 d(i)=0 d(i)=0 l ( i ) = e ( i ) l(i)=e(i) l(i)=e(i) 的活动 a i a_i ai关键活动。由关键活动组成的路径就是关键路径

求关键路径的步骤

  1. 求所有事件的最早发生时间 v e ( ) ve() ve()

    拓扑排序序列,依次求各个顶点的 v e ( k ) ve(k) ve(k)
    v e ( 源 点 ) = 0 v e ( k ) = Max { v e ( j ) + Weight ( v j , v k ) } , v j 为 v k 的 任 意 前 驱 ve(源点)=0\\ ve(k)=\text{Max}\{ve(j)+\text{Weight}(v_j,v_k) \},v_j为v_k的任意前驱 ve()=0ve(k)=Max{ve(j)+Weight(vj,vk)},vjvk
    在这里插入图片描述

  2. 求所有事件的最迟发生时间 v l ( ) vl() vl()

    逆拓扑排序序列,依次求各个顶点的 v l ( k ) vl(k) vl(k)
    v l ( 汇 点 ) = v e ( 汇 点 ) = 0 v l ( k ) = Min { v l ( j ) − Weight ( v k , v j ) } , v j 为 v k 的 任 意 后 继 vl(汇点)=ve(汇点)=0\\ vl(k)=\text{Min}\{vl(j)-\text{Weight}(v_k,v_j) \},v_j为v_k的任意后继 vl()=ve()=0vl(k)=Min{vl(j)Weight(vk,vj)},vjvk
    在这里插入图片描述

  3. 求所有活动的最早发生时间 e ( ) e() e()

    若边 ⟨ v k , v j ⟩ \lang v_k,v_j\rang vk,vj表示活动 a i a_i ai,则有 e ( i ) = v e ( k ) e(i)=ve(k) e(i)=ve(k)
    在这里插入图片描述

  4. 求所有活动的最迟发生时间 l ( ) l() l()

    若边 ⟨ v k , v j ⟩ \lang v_k,v_j\rang vk,vj表示活动 a i a_i ai,则有 l ( i ) = v l ( j ) − Weight ( v k , v j ) l(i)=vl(j)-\text{Weight}(v_k,v_j) l(i)=vl(j)Weight(vk,vj)
    在这里插入图片描述

  5. 求所有活动的时间余量 d ( l ) d(l) d(l)
    d ( i ) = l ( i ) − e ( i ) d(i) = l(i)-e(i) d(i)=l(i)e(i)
    在这里插入图片描述

    d ( k ) = 0 d(k)=0 d(k)=0的活动就是关键活动( a 2 , a 5 , a 7 a_2,a_5,a_7 a2,a5,a7),由关键活动可得关键路径( V 1 → V 3 → V 4 → V 6 V_1\rightarrow V_3\rightarrow V_4 \rightarrow V_6 V1V3V4V6)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quentin_HIT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值