王道数据结构笔记(6)-图论

第六章 图

6.1 图的基本概念

6.1.1 图的定义

图G由顶点集V边集E组成,记为G=(V,E)
V(G)表示图G顶点的有限非空集
E(G)表示图G顶点之间的关系(边)集合。
|V|表示图G中顶点的个数,也称图G的阶
|E|表示G中边的条数

1. 有向图

若E是有向边(弧) 的有限集合,则图G为有向图。是顶点的有序对,记为<v,w>,v称为弧尾,w称为弧头,<v,w>称为从顶点v到顶点w的弧。

2. 无向图

若E是无向边(边) 的有限集合,则图G为无向图。是顶点的无序对,记为(v,w)或(w,v),称顶点w和顶点v互为邻接点

3. 简单图、多重图

不存在重复边,不存在顶点到自身的边,为简单图,否则为多重图

4. 完全图(简单完全图)

完全图中任意两个顶点之间都存在边

无向图|E|取值范围为**[0, n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1)]**

有向图|E|取值范围为**[0,n(n-1)]**

5.子图

子图:设有两个图G(V,E),G’(V’,E’),若V’为V子集,E’为E子集,则称G’为G子图
生成子图:满足V(G’)=V(G) 的子图G’为G的生成子图

6. 连通、连通图和连通分量

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

若图G中任意两个顶点都连通,则图G为连通图,否则为非连通

无向图中的极大连通子图称为连通分量

对于n个顶点的无向图G:
连通图最少有n-1条边
非连通图最多有 C n 2 − 1 C^2_n-1 Cn21条边

7. 强连通图、强连通分量

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

若图中任何一对顶点都是强连通的,则称图为强连通图

有向图中的极大强连通子图称为有向图的强连通分量

强连通图最少有n条边,以构成环路

8. 生成树、生成森林

连通图的生成树是包含图中全部顶点极小连通子图(不存在回路)

在非连通图中,连通分量的生成树构成了非连通图的生成森林

图有n个顶点,则生成树含有n-1条边

n个顶点的图,若|E|>n-1,则一定有回路

9. 顶点的度、入度和出度

对于无向图:

  • 顶点v的度为依附该顶点的边的条数,记为TD(v)

  • 全部顶点的度之和等于边数的2倍

对于有向图:

  • 入度为以顶点v为终点的有向边的数目,记为ID(v)

  • 出度为以顶点v为起点的有向边的数目,记为OD(v)

  • 顶点v的度:TD(v)=ID(v)+OD(v)

  • n个顶点,e条边的有向图中, ∑ i = 1 n \sum\limits_{i=1}^{n} i=1nID( v i v_i vi)= ∑ i = 1 n \sum\limits_{i=1}^{n} i=1nOD( v i v_i vi)=e

10. 边的权、带权图/网

边的权:边具有的某种含义的数值

带权图/网:边上带有权值的图

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

11.特殊形态的图

无向完全图:无向图中任意两个顶点之间存在边,若|V|=n,则|E| ∈ \in [0, C n 2 C^2_n Cn2]=[0,n(n-1)/2]

有向完全图:有向图中任意两个顶点之间存在方向相反的两条弧,若|V|=n,则|E| ∈ \in [0,2 C n 2 C^2_n Cn2]=[0,n(n-1)]

稀疏图、稠密图

有向树:一个顶点入度为0,其余顶点入度均为1的有向图

12. 顶点-顶点的关系描述
  • 路径:顶点 v p v_p vp到顶点 v q v_q vq之间的一条路径是指顶点序列, v p v_p vp, v 1 v_1 v1, v 2 v_2 v2,…, v n v_n vn, v q v_q vq

  • 回路:第一个顶点和最后一个顶点相同的路径

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

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

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

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

  • 极大:如果此时加入任何一个不在图的点集中的点都会导致它不再连通

  • 极小:如果删除一条边,就无法构成生成树

6.2图的存储及基本操作

6.2.1 邻接矩阵法

用一个一维数组存储图中顶点的信息,用一个二维数组存储途中边的信息。

邻接矩阵存储结构定义
#define MaxVertexNum 100//顶点数最大值
typedef struct{
	VertexType Vex[MaxVertexNum];//顶点数据类型
	EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵边表
	int vexnum,arcnum;//图当前定点数和弧数
}MGraph;
邻接矩阵法的特点
  • 适合存储稠密图

  • 无向图:

    • 第i个顶点的:第i行(列)的非零元素个数

    • 复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

    • 无向图的邻接矩阵一定是对称矩阵,因此只需要存储上或下三角矩阵的元素

  • 有向图:

    • 第i个顶点的出度=第i的非零元素个数

      • 第i个顶点的入度=第i的非零元素个数

      • 第i个顶点的度=第i行、第i列的非零元素个数之和

      • 复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

  • 容易确认任意两个顶点是否相连,但确认有多少条边必须遍历全图,花费时间代价大

  • 邻接矩阵空间复杂度: O ( n 2 ) O(n^2) O(n2)——只与顶点数相关

  • 设图G的邻接矩阵为A(只包含0和1),则 A n A^n An的元素 A n A^n An[i][j]等于由顶点i到顶点j的长度为n的路径的数目(用离散数学论证)

    在这里插入图片描述

    A = [ 0 1 0 0 1 0 1 1 0 1 0 1 0 1 1 0 ] A= \begin{bmatrix} 0&1&0&0\\ 1&0&1&1\\ 0&1&0&1\\ 0&1&1&0\\ \end{bmatrix} A= 0100101101010110 A 2 = [ 1 0 1 1 0 3 1 1 1 1 2 1 1 1 1 2 ] A^2=\begin{bmatrix} 1&0&1&1\\ 0&3&1&1\\ 1&1&2&1\\ 1&1&1&2\\ \end{bmatrix} A2= 1011031111211112 A 3 = [ 0 3 1 1 3 2 4 4 1 4 2 3 1 4 3 2 ] A^3=\begin{bmatrix} 0&3&1&1\\ 3&2&4&4\\ 1&4&2&3\\ 1&4&3&2\\ \end{bmatrix} A3= 0311324414231432

6.2.2 邻接表法

对图G中的每个顶点 V i V_i Vi建立一个单链表,第i个单链表的节点表示依附于顶点 V i V_i Vi的边(或以 V i V_i Vi为尾的弧),该表称为顶点 V i V_i Vi的边表(有向图称出边表)。

在这里插入图片描述

[1]:2→5→3→4
[2]:1→3→4
[3]:2→5→1
[4]:2→1
[5]:3→1
[1]:5
[2]:1
[3]:
[4]:2→3→1
[5]:5
邻接表存储结构定义
#define MVN 100
typedef struct ArcNode{//边表节点
	int adjvex;
	struct ArcNode *next;
	//InfoType info;//网的边权值
}ArcNode;

typedef struct VNode{//顶点边节点
	VertexType data;//顶点信息
	ArcNode *first;
}VNode,AdjList[MVN];

typedef struct{
	AdjList vertices;//邻接表
	int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//以邻接表存储的图类型

邻接表法的特点
  • 稀疏图采用邻接表法能极大节省存储空间
  • 无向图存储空间为** O ( ∣ V ∣ + 2 ∣ E ∣ ) O(|V|+2|E|) O(V+2∣E);有向图存储空间为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)**。无向图每条边在表中都出现了两次
  • 查找顶点的所有邻边,邻接表比邻接矩阵效率高查找两顶点是否存在边,邻接表比邻接矩阵效率低
  • 在有向图邻接表中,计算一个顶点的结点个数即得到该顶点的出度,求其入度需要遍历全表,可以采用逆邻接表方式加速入度求解速度。
  • 图邻接表表示不唯一,各边结点的链接次序可以任意排列

6.2.3 十字链表法

十字链表法只用于有向图,使用条件:

  • 当用邻接矩阵存储时:空间复杂度为 O ( ∣ v ∣ 2 ) O(|v|^2) O(v2),太大
  • 当用邻接表法存储时:在进行入度查询时只能进行全部节点遍历,很不方便
    在这里插入图片描述

数据结构:

在这里插入图片描述

十字链表法表示:

在这里插入图片描述

图的十字链表表示并不唯一,但一个表示确定一个图

6.2.4 邻接多重表

邻接多重表是无向图的另一种链式存储结构,邻接多重表仅适用于存储无向图或无向网。

图:

在这里插入图片描述

数据结构:

在这里插入图片描述

邻接多重表法表示:

在这里插入图片描述

6.2.5 图的基本操作

函数名作用描述
adjacent(G,x,y)判断图G这个i部分是否存在边
neighbors(G,x)列出图G中与结点x邻边的边
insertVertex(G,x)在图G中插入顶点x
deleteVertex(G,x)在图G中删除顶点x
addEdge(G,x,y)若(x,y)或<x,y>不存在,则向图G添加该边
removeEdge(G,x,y)若(x,y)或<x,y>存在,则向图G删除该边
firstNeighbor(G,x)求图G中顶点x的第一个邻接点,若有返回顶点号,若x无临界点或不存在x返回-1
nextNeighbor(G,x,y)假设图G中顶点y是顶点x的一个邻接点,返回返回x的除y以外下一个邻接点的顶点号。若y是x最后一个邻接点,返回-1
getEdgeValue(G,x,y)获得图G中(x,y)或<x,y>对应权值
setEdgeValue(G,x,y,v)设置图G中(x,y)或<x,y>对应权值为v

图的遍历算法:深度优先遍历和广度优先遍历

6.3 图的遍历

6.3.1 广度优先搜索

BFS基本思路:先访问起始顶点v,由v出发一次访问未访问过的邻接顶点 w 1 , w 2 , w 3 . . . w i w_1,w_2,w_3...w_i w1,w2,w3...wi,然后依次访问 w 1 , w 2 , w 3 . . . w i w_1,w_2,w_3...w_i w1,w2,w3...wi的未被访问的邻接顶点,直到所有顶点都被访问。若此时仍有顶点未被访问,则另选图中未被访问的顶部作为起始点重复上述过程。

Dijkstra单源最短路径算法和Prim最小生成树算法应用了此类思想

广度优先搜索是一种分层的查找过程,不是一个递归算法。为实现逐层访问必须结组辅组队列记忆正在访问的顶点的下一层顶点。

在这里插入图片描述

该图使用广度优先搜索顺序:ABCDEFG

广度优先搜索遍历算法是二叉树的层次遍历算法扩展

bool visited[MAX_VERTEX_NUM];//访问标记数组
 
//广度优先遍历
//1.将根节点放入队列中
//2.从队列中取出第一个元素,将队列中的第一个元素弹出
//3.将所取得元素的全部未访问过的节点加入队列中
//4.判断队列是否为空
//     a. 若是,则结束
//     b.若不是,则跳到第二步
void BFS(Graph G,int v){        //从顶点v出发,广度优先遍历图G
    visit(v);            //访问初始顶点v
    visited[v] = TRUE;        //对v做已访问标记
    Enqueue(Q,v);        //根节点放入队列中,此时队列只有根结点一个元素
    while(!isEmpty(Q)){
        DeQueue(Q,u);        //从队列中取出第一个元素,将队列中的第一个元素弹出
        for( w = FirstNeighbor(G,u); w >= 0; w = NextNeighbor(G,u,w))
            //检测v所有邻接点
            if(!visited[w]){        //w为v的未访问过的邻接顶点
                visit(w);            //访问顶点w
                visited[w] = TRUE;        //对w做已访问标记
                EnQueue(Q,w);        //顶点w入队列
        }//if
    }//while
}
  1. BFS算法的性能分析

    BFS算法需要借助辅组队列Q,n个顶点均需入队一次,空间复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

    采用邻接表法时,每个顶点结点和边结点均需搜索一次,时间复杂度为 O ( ∣ E ∣ + ∣ V ∣ ) O(|E|+|V|) O(E+V);
    采用邻接矩阵法时,查找每个顶点的邻接点所需时间为O(|V|),故算法总时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

  2. BFS算法求解单源最短路径问题

    BFS算法也有相应的缺点,它仅仅适用于无权图,或者所有边权值都相同的图

    若图G=(V,E)为非带权图,定义从顶点u到顶点v的最短路径d(u,v)为从u到v的任何路径中最少的边数,若没有通路,则 d ( u , v ) = ∞ d(u,v)=\infty d(u,v)=

    //求顶点u到其他顶点的最短路径
    //d[]用于存储目标节点到其余节点的最短路径长度
    //path[]用于存储其余节点的直接前驱
    void BFS_Distance(Graph G,int v){
        for(i = 0;i < G.vexnum;++i){//初始化路径长度
            d[i] = MAX;
            path[i] = -1;
        }
        d[v] = 0;//将v到v的距离设为0
        visited[v] = TRUE;//将u标记为已访问
        EnQueue(Q,v);
        while(!isEmpty(Q)){
            DeQueue(Q,u);//从队列中取出第一个元素,将队列中的第一个元素弹出
            for(w = FirstNeighbor(G,u);w >= 0;w = NextNeighbor(G,u,w)){
    			if(!visited[w]){//w为v的未访问过的邻接顶点
                    d[w] = d[u] + 1;//w的距离为上一个邻接顶点的距离+1
                    path[w] = u;//储存w到v的直接前驱
                    visited[w] = TRUE;//标记为已访问
                    EnQueue(Q,w);//将w入队
            	}
            }
        }
    }
    
    
  3. 广度优先生成树

    在广度遍历的过程中得到的遍历树。

    给定图的邻接矩阵存储表示是唯一的,因此其广度优先生成树唯一。

    给定图的邻接表存储表示不唯一,因此其广度优先生成树不唯一。

6.3.2 深度优先搜索

DFS类似树的先序遍历。

基本思路:从图中某一起始顶点v出发,访问与v邻接且未被访问过的任意顶点 w 1 w_1 w1,再访问与 w 1 w_1 w1邻接且未被访问的任意顶点 w 2 w_2 w2,重复上述过程,当不能继续向下访问时依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述过程,直到图中所有顶点均被访问。

bool visited[MAX_VERTEX_NUM];
void DFS(Graph G, int v){
    //将v设为已访问
    visit(v);
    visited[v]=TRUE;
    for(w=firstNeighbor(G,v);w>=0;w=nextNeighbor(G,v,w)){
        //w为未被访问的顶点
        if(!visited[w]){
            DFS(G,w);
        }
    }
}

在这里插入图片描述

该图使用深度优先搜索顺序:ABDEHCFG

对于同一个图,基于邻接矩阵的遍历得到的DFS序列和BFS序列是唯一的,基于邻接表的遍历得到的DFS序列和BFS序列是不唯一的

  1. DFS算法性能分析

    DFS算法是递归算法,需要借助递归工作栈,空间复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

    使用邻接矩阵表示时查找每个顶点的邻接点所需的时间为 O ( ∣ V ∣ ) O(|V|) O(V),总时间复杂度为 O ( ∣ V 2 ∣ ) O(|V_2|) O(V2)

    使用邻接表表示时,查找所有顶点的邻接点所需的时间为 O ( ∣ E ∣ ) O(|E|) O(E),访问顶点所需时间为 O ( ∣ V ∣ ) O(|V|) O(V),此时总复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

  2. 深度优先的生成树和生成森林

    对连通图调用DFS才能产生深度优先树,否则产生的将是深度优先森林。

    基于邻接表的深度优先数是不唯一的。

6.3.3 图的遍历与图的连通性

对于无向图来说,若无向图连通,则从任意结点出发仅需一次遍历就能访问图中的所有顶点;对于有向图来说,若从初始点到图中的每一个顶点都有路径,则能够访问到图中的所有顶点。

非强连通分量一次调用BFS(G,i)或DFS(G,i)无法访问该连通分量的所有顶点。

6.4 图的应用

6.4.1 最小生成树

设Z为G的所有生成树的集合,若T为Z中边的权值之和最小的那颗生成树,则T称为G的最小生成树。

最小生成树性质:

  1. 树形不唯一,Z中可能有多个最小生成树。当G中各边权值互不相同时G的最小生成树唯一。若G本身是一棵树,则G的最小生成树是它本身。
  2. 最小生成树的边的权值之和总是唯一的,且是最小的。
  3. 最小生成树边数为顶点数-1

Prim算法和kruskal算法都基于贪心算法

1. Prim(普里姆)算法
  1. 初始时任取一顶点加入树T
  2. 选择一个与当前T中顶点集合距离最近的顶点,将该顶点和相应边加入T
  3. 每次操作后T的顶点数和边数都加1
  4. 重复2,直到图中所有顶点都并入T,得到T的最小生成树。

Prim算法时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2),不依赖|E|,适合求解边稠密图。采用其他方法可以改进时间复杂度,但增加了实现复杂性。

在这里插入图片描述

2. Kruskal(克鲁斯卡尔)算法
  1. 每个顶点都构成一颗独立的树,T是一个仅含|V|个顶点的森林
  2. 按边的权值递增排序,依次从 E E E- E T E_T ET中选择一条边,若加入T后不构成回路则加入 E T E_T ET,否则舍弃
  3. 重复2直到 E T E_T ET有n-1条边

在Kruskal算法中通常采用堆存放边的合集,因此每次选择最小权值边时间复杂度为 O ( l o g ∣ E ∣ ) O(log|E|) O(logE)。可以采用并查集的数据结构描述T,因此构造T的时间复杂度为 O ( ∣ E ∣ l o g ∣ E ∣ ) O(|E|log|E|) O(ElogE)

Kruskal算法适合于边稀疏顶点多的图

在这里插入图片描述

6.4.2 最短路径

带权路径长度最短的路径称为最短路径

求最短路径的算法通常依赖一种属性:两点之间的最短路径也包含了路径上其他顶点间的最短路径。

带权图的最短路径问题通常分为两类:

  1. 求图中某一点到其他各顶点的最短路径(单源最短路径)——用Dijkstra算法求解
  2. 求每对顶点间的最短路径——用Floyd算法求解
1. Dijkstra算法求单源最短路径问题

迪杰斯特拉最朴素的思想就是按长度递增的次序产生最短路径。即每次对所有可见点的路径长度进行排序后,选择一条最短的路径,这条路径就是对应顶点到源点的最短路径。

u为给定的顶点,求u到每个顶点的最短路径集合Dist

需要创建三个辅助数组

Dist[x]记录u到x顶点的最短路径权值和

Path[x]记录u→x的最短路径上x的前驱顶点号

s[x]表示x已求得最短路径长度值,每一轮广度优先遍历的到的路径值中最短的路径值为最短路径。(如下图C可走的顶点只有AB,若C不通过C->B到达B,只能通过C->A->B到达B,这样的话C到达B的路径值则为10+,必定大于C->B=5,因此C->B必定为C到B的最短路径)

在这里插入图片描述

设C点为给定顶点u

在这里插入图片描述

Dijkstra算法并不适用边带有负权值的图

2. Floyd(弗洛伊德)算法求各顶点之间最短路径问题

用A保存当前各点之间最短的距离

用PATH保存各点之间的中转顶点

在这里插入图片描述

  1. 初始化,创建邻接矩阵 A [ − 1 ] A^{[-1]} A[1]保存各点之间的直接距离(不允许任何点当中转点), P A T H [ − 1 ] PATH^{[-1]} PATH[1]全为-1

    在这里插入图片描述

  2. 设允许A点中转,求 A [ 0 ] A^{[0]} A[0] P A T H [ 0 ] PATH^{[0]} PATH[0]


    A ( k − 1 ) [ i ] [ j ] > A ( k − 1 ) [ i ] [ k ] + A ( k − 1 ) [ k ] [ j i → j 权值 > i → k 权值 + k → j 权值 A^{(k-1)}[i][j]>A^{(k-1)}[i][k]+A^{(k-1)}[k][j\\ i\to j 权值>i\to k权值+k\to j权值 A(k1)[i][j]>A(k1)[i][k]+A(k1)[k][jij权值>ik权值+kj权值

    A k [ i ] [ j ] = A ( k − 1 ) [ i ] [ k ] + A ( k − 1 ) [ k ] [ j ] i → j 权值 = i → k 权值 + k → j 权值 p a t h ( k ) [ i ] [ j ] = k A^{k}[i][j]=A^{(k-1)}[i][k]+A^{(k-1)}[k][j]\\ i\to j 权值=i\to k权值+k\to j权值\\ path^{(k)}[i][j]=k Ak[i][j]=A(k1)[i][k]+A(k1)[k][j]ij权值=ik权值+kj权值path(k)[i][j]=k

    k=A
    [B][A]+[A][C]=MAX
    [B][A]+[A][D]=4<[B][D]=9 ->path[B][D]=A
    [B][A]+[A][E]=MAX
    [C][A]+[A][B]=12>[C][B]=5 
    [C][A]+[A][D]=11<[C][D]=MAX ->path[C][D]=A
    [C][A]+[C][E]=MAX
    [D][A]=MAX
    [E][A]=MAX
    

    在这里插入图片描述

  3. 设允许B点中转,求 A [ 1 ] A^{[1]} A[1], P A T H [ 1 ] PATH^{[1]} PATH[1]

    k=B
    [A][B]+[B][C]=MAX
    [A][B]+[B][D]=6>[A][D]=1
    [A][B]+[B][E]=4<[A][E]=MAX->path[A][E]=B
    [C][B]+[B][A]=8<[C][A]=10->path[C][A]=B
    [C][B]+[B][D]=9<[C][D]=11->path[C][D]=B
    [C][B]+[B][E]=7<[C][E]=MAX->path[C][E]=B
    [D][B]=MAX
    [E][B]=MAX
    

    在这里插入图片描述

  4. 设允许C点中转,求 A [ 2 ] A^{[2]} A[2], P A T H [ 2 ] PATH^{[2]} PATH[2]

    k=C
    [A][C]=MAX
    [B][C]=MAX
    [D][C]=MAX
    [E][C]+[C][A]=15<[E][A]=MAX->path[E][A]=C
    [E][C]+[C][B]=12<[E][B]=MAX->path[E][B]=C
    [E][C]+[C][D]=16>[E][D]=6
    

    在这里插入图片描述

  5. 设允许D点中转,求 A [ 3 ] A^{[3]} A[3], P A T H [ 3 ] PATH^{[3]} PATH[3]

    k=D
    [D][A]=MAX
    [D][B]=MAX
    [D][C]=MAX
    [A][D]+[D][E]=5>[A][E]=4
    [B][D]+[D][E]=8>[B][E]=2
    [C][D]+[D][E]=13>[C][E]=7
    

    在这里插入图片描述

  6. 设允许E点中转,求 A [ 4 ] A^{[4]} A[4], P A T H [ 4 ] PATH^{[4]} PATH[4],求得各点之间最短的距离 A [ 4 ] A^{[4]} A[4]

    k=E
    [A][E]+[E][B]=19>[A][B]=12
    [A][E]+[E][C]=11<[A][C]=MAX->path[A][C]=E
    [A][E]+[E][D]=10>[A][D]=1
    [B][E]+[E][A]=17>[B][A]=3
    [B][E]+[E][C]=9<[B][C]=MAX->path[B][C]=E
    [B][E]+[E][D]=8>[B][D]=4
    [C][E]+[E][A]=22>[C][A]=8
    [C][E]+[E][B]=19>[C][B]=5
    [C][E]+[E][D]=13>[C][D]=9
    [D][E]+[E][A]=19<[D][A]=MAX->path[D][A]=E
    [D][E]+[E][B]=16<[D][B]=MAX->path[D][B]=E
    [D][E]+[E][C]=11<[D][C]=MAX->path[D][C]=E
    

    在这里插入图片描述

Floyd算法时间复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(V3),代码紧凑不包含其他数据结构,即使对中等规模的输入也相当有效

Floyd算法允许图中有带负权值的边,但不允许有包含带负权值边组成的回路。

6.4.3 有向无环图描述表达式

有向无环图:DAG,有向图中不存在环路。

DAG是描述含公共子式的表达式的有效工具
( ( a + b ) ∗ b ∗ ( c + d ) ) + ( c + d ) ∗ e ) ∗ ( ( c + d ) ∗ e ) ((a+b)*b*(c+d))+(c+d)*e)*((c+d)*e) ((a+b)b(c+d))+(c+d)e)((c+d)e)
使用二叉树描述表达式

在这里插入图片描述

使用DAG表述表达式

在这里插入图片描述

6.4.4 拓扑排序

AOV网:使用DAG图表示工程,用顶点表示活动,用有向边< V i V_i Vi, V j V_j Vj>表示活动 V i V_i Vi必须先于活动 V j V_j Vj进行,这种有向图称为顶点表示活动的网络

任何活动 V i V_i Vi都不能以自己作为自己的前驱或后继

拓扑排序:

  • 定义一:

    由一个DAG的顶点组成的序列,并满足以下条件:

    1. 每个顶点出现且只出现一次
    2. 若A排在B的前面,则图中不存在B到A的路径
  • 定义二:

    是对DAG的顶点的一种排序,使得若 ∀ \forall <A,B>路径,则排序中B出现在A的后面。

每个AOV图有一个或多个拓扑排序序列。

常用拓扑排序

  1. 从AOV网选择一个没有前驱的顶点并输出
  2. 从网中删除该顶点和所有以它为起点的有向边
  3. 重复1.2.直到当前的AOV网为空或不存在无前驱的顶点为止。后者说明图必存在环。

在这里插入图片描述

采用邻接表存储拓扑排序的时间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E)

采用邻接矩阵存储拓扑排序的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

逆拓扑排序:

  1. 从AOV网选择一个没有后继的顶点并输出
  2. 从网中删除该顶点和所有以它为终点的有向边
  3. 重复1.2.直到AOV网为空

注意:

若一个顶点有多个直接后继,则拓扑排序的结果通常不唯一;若各个顶点已经排在一个线性有序的序列中,每个顶点有唯一的前驱后继关系,则拓扑排序结果唯一。

对于一般图来说,若其邻接矩阵是三角矩阵,则存在拓扑序列,反之则不一定成立。

6.4.5 关键路径

AOE网:带权DAG中,顶点表示事件,有向边表示活动,边上权值表示完成活动的开销,称为用边表示活动的网络

AOE性质:

  1. 只有在顶点所代表的事件发生后,从该顶点出发的各个有向边代表的活动才能开始
  2. 只有在进入定点额度各个有向边代表的活动结束时才能发生该顶点代表的事件

AOE网只有一个入度为0的顶点(开始顶点,源点),只有一个出度为0的顶点(结束顶点,汇点)

从源点到汇点的所有路径中具有最大路径长度的路径称为关键路径,关键路径上的活动称为关键活动

寻找关键路径需要的参数:

  1. 事件 V k V_k Vk的最早发生时间** v e ( k ) ve(k) ve(k)**

    从源点 V 1 V_1 V1到顶点 V k V_k Vk的最长路径长度。

    V k V_k Vk的最早发生时间决定了所有从 V i V_i Vi开始的活动的能够开工的最早时间

    v e ( 源点 ) = 0 v e ( k ) = M a x { v e ( k − 1 ) + W e i g h t ( V k − 1 , V k ) } V k 为 V k − 1 的任意后继, W e i g h t ( V k − 1 , V k ) 表示 < V k − 1 , V k > 上的权值 ve(源点)=0 \\ ve(k)=Max\{ve(k-1)+Weight(V_{k-1},V_k)\}\\V_k为V_{k-1}的任意后继,Weight(V_{k-1},V_k)表示<V_{k-1},V_k>上的权值 ve(源点)=0ve(k)=Max{ve(k1)+Weight(Vk1,Vk)}VkVk1的任意后继,Weight(Vk1,Vk)表示<Vk1,Vk>上的权值

  2. 事件 V k V_k Vk的最迟发生时间** v l ( k ) vl(k) vl(k)**

    在不推迟整个工程的前提下该事件最迟必须发生的时间

    v l ( 汇点 ) = v e ( 汇点 ) v l ( k ) = M i n { v l ( k + 1 ) − W e i g h t ( V k , V k + 1 ) } vl(汇点)=ve(汇点)\\ vl(k)=Min\{vl(k+1)-Weight(V_k,V_{k+1})\} vl(汇点)=ve(汇点)vl(k)=Min{vl(k+1)Weight(Vk,Vk+1)}

  3. 活动 a i a_i ai的最早开始时间** e ( i ) e(i) e(i)**

    活动弧的起点所表示的事件的最早发生事件

    若 < V k , V k + 1 > 代表 a i , 则 e ( i ) = v e ( k ) 若<V_k,V_{k+1}>代表a_i,则\\ e(i)=ve(k) <Vk,Vk+1>代表ai,e(i)=ve(k)

  4. 活动 a i a_i ai最迟开始时间** l ( i ) l(i) l(i)**

    活动弧的终点所表示的事件的最迟发生时间与该活动所需时间之差

    若 < V k , V k + 1 > 代表 a i , 则 l ( i ) = v l ( k + 1 ) − W e i g h t ( V k , V k + 1 ) 若<V_k,V_{k+1}>代表a_i,则\\ l(i)=vl(k+1)-Weight(V_k,V_{k+1}) <Vk,Vk+1>代表ai,l(i)=vl(k+1)Weight(Vk,Vk+1)

  5. 一个活动 a i a_i ai的最迟开始时间 l ( i ) l(i) l(i)和其最早开始时间 e ( i ) e(i) e(i)的差额
    d ( i ) = l ( i ) − e ( i ) d(i)=l(i)-e(i) d(i)=l(i)e(i)

    不增加完成整个工程所需总时间情况下,活动 a i a_i ai可以拖延的时间。

    l ( i ) − e ( i ) = 0 l(i)-e(i)=0 l(i)e(i)=0,说明该活动必须如期完成,即该活动是关键活动。

求关键路径的算法步骤:

  1. 从源点出发, v e ( 源点 ) = 0 ve(源点)=0 ve(源点)=0,按拓扑有序求其余顶点 v e ( ) ve() ve()
  2. 从汇点出发, v l ( 汇点 ) = v e ( 汇点 ) vl(汇点)=ve(汇点) vl(汇点)=ve(汇点),按拓扑有序求其余顶点 v l ( ) vl() vl()
  3. 根据各顶点额度 v e ( ) ve() ve()值求所有弧的 e ( ) e() e()
  4. 根据各顶点额度 v l ( ) vl() vl()值求所有弧的 l ( ) l() l()
  5. 求所有活动的差额 d ( ) d() d(),找出所有 d ( ) = = 0 d()==0 d()==0的活动构成关键路径

在这里插入图片描述

V 1 V_1 V1 V 2 V_2 V2 V 3 V_3 V3 V 4 V_4 V4 V 5 V_5 V5 V 6 V_6 V6
v e ( k ) ve(k) ve(k)0 v e ( 1 ) + a 1 ve(1)+a_1 ve(1)+a1
3
v e ( 1 ) + a 2 ve(1)+a_2 ve(1)+a2
2
v e ( 3 ) + a 5 ve(3)+a_5 ve(3)+a5
6
v e ( 2 ) + a 4 ve(2)+a_4 ve(2)+a4
6
v e ( 4 ) + a 7 ve(4)+a_7 ve(4)+a7
8
v l ( k ) vl(k) vl(k)0 M i n { v e ( 5 ) − a 4 = 4 , v e ( 4 ) − a 3 = 4 } Min\{ve(5)-a_4=4,ve(4)-a_3=4\} Min{ve(5)a4=4,ve(4)a3=4}
4
M i n { v l ( 4 ) − a 5 = 2 , v l ( 6 ) − a 6 = 5 } Min\{vl(4)-a_5=2,vl(6)-a_6=5\} Min{vl(4)a5=2,vl(6)a6=5}
2
v l ( 6 ) − a 7 vl(6)-a_7 vl(6)a7
6
v l ( 6 ) − a 8 vl(6)-a_8 vl(6)a8
7
v e ( 6 ) ve(6) ve(6)
8
a 1 a_1 a1 a 2 a_2 a2 a 3 a_3 a3 a 4 a_4 a4 a 5 a_5 a5 a 6 a_6 a6 a 7 a_7 a7 a 8 a_8 a8
e ( i ) = v e ( k ) e(i)=ve(k) e(i)=ve(k) v e ( 1 ) ve(1) ve(1)
0
v e ( 1 ) ve(1) ve(1)
0
v e ( 2 ) ve(2) ve(2)
3
v e ( 2 ) ve(2) ve(2)
3
v e ( 3 ) ve(3) ve(3)
2
v e ( 3 ) ve(3) ve(3)
2
v e ( 4 ) ve(4) ve(4)
6
v e ( 5 ) ve(5) ve(5)
6
l ( i ) = l(i)= l(i)=
v l ( k + 1 ) − W e i g h t ( V k , V k + 1 ) vl(k+1)-Weight(V_k,V_{k+1}) vl(k+1)Weight(Vk,Vk+1)
v l ( 2 ) − a 1 vl(2)-a_1 vl(2)a1
1
v l ( 3 ) − a 2 vl(3)-a_2 vl(3)a2
0
v l ( 4 ) − a 3 vl(4)-a_3 vl(4)a3
4
v l ( 5 ) − a 4 vl(5)-a_4 vl(5)a4
4
v l ( 4 ) − a 5 vl(4)-a_5 vl(4)a5
2
v l ( 6 ) − a 6 vl(6)-a_6 vl(6)a6
5
v l ( 6 ) − a 7 vl(6)-a_7 vl(6)a7
6
v l ( 6 ) − a 8 vl(6)-a_8 vl(6)a8
7
d ( i ) = l ( i ) − e ( i ) d(i)=l(i)-e(i) d(i)=l(i)e(i)10110301

注意事项:

  1. 关键路径上所有活动都是关键活动,是决定整个工程的关键因素。可以通过加快关键活动缩短工程工期,但缩短到一定程度会变成非关键活动
  2. 关键路径不唯一,对于有多个关键路径的网,只提高一条关键路径上的关键活动速度并不能缩短整个工程工期,必须加快所有关键路径上的关键活动才能达到缩短工期目的
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值