用一个一维数组存储图中顶点的信息,存储顶点信息的一维数组称为顶点表;
用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。

一个含有n个顶点的图,其邻接矩阵(Adjacency Matrix)是具有如下性质的n阶方阵:(
v
i
、
v
j
v_i、v_j
vi、vj 是图的顶点)
A
[
i
]
[
j
]
=
{
1
,若弧
<
v
i
,
v
j
>
或边
(
v
i
,
v
j
)
存在
0
,反之邻边不存在
A[i][j]={1,若弧<vi,vj>或边(vi,vj)存在0,反之邻边不存在
A[i][j]={1,若弧<vi,vj>或边(vi,vj)存在0,反之邻边不存在
若是含有n个顶点的网,其邻接矩阵(Adjacency Matrix)则是有如下性质的n阶方阵:(
w
i
,
j
w_{i,j}
wi,j 是边的权值)
A
[
i
]
[
j
]
=
{
w
i
,
j
,若弧
<
v
i
,
v
j
>
或边
(
v
i
,
v
j
)
存在
∞
,反之邻边不存在
A[i][j]={wi,j,若弧<vi,vj>或边(vi,vj)存在∞,反之邻边不存在
A[i][j]={wi,j,若弧<vi,vj>或边(vi,vj)存在∞,反之邻边不存在

对于无向图的邻接矩阵:
对于有向图的邻接矩阵:
邻接表是图的一种链式存储结构,为每个顶点
v
i
v_i
vi 建立了一个单链表存储依附于它的边
(
v
i
,
v
j
)
(v_i,v_j)
(vi,vj) (如果是有向图则是以顶点
v
i
v_i
vi 为尾的弧
<
v
i
,
v
j
>
在无向图中顶点
v
i
v_i
vi 的度恰为第 i 个链表中的边结点个数。
在有向图中第 i 个链表中的边结点个数只是顶点
v
i
v_i
vi 的出度,要求入度则需遍历整个邻接表(或构建一个逆邻阶表)。

【注意】
① 图的邻接表和二叉树的孩子链表用的是同一存储结构:顺序表+链表(主要是链式存储);
② 由于单链表中各边结点的连接次序可以是任意的,所以图的邻接链表表示法不唯一;
③ 我们之前提到了图是可以生成树和森林的,因为树的双亲孩子关系与有向图的首尾关系非常相似,所以图和树的存储方式是可以相互参考的(博主不确定方法提出的先后)。
无向图的邻接表容易求得顶点和边的各种信息,但是由于一条边被存储在相关联的两个顶点的边链表中,对边执行标记与删除等操作时需要分别在两个链表中遍历查找结点,效率较低。
改进后的邻接多重表是更适合无向图执行这类操作的一种链式存储结构(主要是链式存储,包含顺序存储的顶点表)。

顶点结点中包含两个域:
边结点中包含六个域:(数据域和指针域都是“i 在前,j 在后”)

【说明】
① 上图中是没有添加mark标志域和info指针域的邻接多重表。
② 其实无向图的邻接表和邻接多重表的各种基本操作和实现都是类似的,区别就在于同一条边在邻接多重表中仅有一个结点。
③ 通常边结点存储在数组下标靠前的顶点的边链表中,而另一个顶点指向之前已经创建的边结点即可。如果上方创建了多个包含当前顶点的边结点,一般指向离自己最近的那个(如上图中顶点 d 指向了边(c,d)所在的结点)。

绘制的邻接多重表要以美观为主:(便于观察)
在邻接链表中我们提到了计算有向图的入度十分困难,往往需要遍历整个邻接表或构建逆邻接表。
改进后的十字链表是更方便有向图计算入度和出度的一种链式存储结构(主要是链式存储,包含顺序存储的顶点表)。
顶点结点中包含三个域:(指针域“头在前,尾在后”)
弧结点中包含五个域:(数据域“发出(尾)在前,接收(头)在后”)

【说明】
① 上图中是没有添加info指针域的十字链表。
② 可以看到绘制十字链表的弧结点时数据域所在位置(头两个)和邻接多重链表(第一、三个)是不一样的,并且不含标志域mark。
③ 十字链表也和多重链表一样,弧结点之间均没有先后顺序之分,所以有向图的十字链表表示、无向图的多重链表表示不唯一。但是反过来一个十字链表可以确定一个有向图,一个多重链表可以确定一个无向图。


【分析】邻接表、邻接矩阵都是既可以存储有向图又可以存储无向图的,且我们得到的是一个对称的邻接矩阵,所以图 G 有两种可能。
图含有大量的专业术语,第一部分中分为15个小点;
图还有四种存储方式,都是顺序+链式存储结构,两种即可存储有向图又可存储无向图,两种分别只能存储无向图或有向图。
深度优先搜索也叫深度优先遍历,它的核心思想就是:深究到底。
① 我们先选中某一顶点作为起点,访问该顶点;
② 然后在当前顶点的邻接点中选择一个未被访问过的顶点,访问该顶点。重复该步骤直到当前顶点不存在未被访问的邻接点;
③ 回到最近访问过的一个顶点,要求该顶点还存在未被访问的邻接点,选择其中一个(未被访问的)顶点,访问该顶点;
④ 重复步骤二和三,直到图中所有顶点都被访问过。
由于邻接点选择的不同,访问的顺序也会不同,所以深度优先遍历得到的序列是不唯一的。

【补充】有向图只能沿着弧的指向走,即当前顶点所邻接到的下一个顶点;
广度优先搜索也叫广度优先遍历,它的核心思想就是:广撒网。
广度优先遍历有点类似于树的层次遍历,但又有着相对严格的顺序要求,这里用到队列来辅助解释遍历过程:
① 我们先选中某一顶点作为起点,将其加入队列,并访问队列中的顶点;
② 将当前顶点的所有未被访问过的邻接点按某一顺序加入队列,然后将当前顶点移出队列;
③ 访问队列中的下一个顶点;
④ 重复步骤二和三,直到队列为空,出队的顺序即为遍历的顺序;
由于入队的顺序不同,访问的顺序也会不同,所以广度优先遍历得到的序列也是不唯一的。

【补充】有向图只能入队以当前顶点为弧尾的弧所关联的顶点,即它所邻接到的顶点;
对于无向图来说:

对于有向图来说:
最小生成树也叫最小代价树,是带权连通图(即连通网)的生成树集合中,各边权值之和最小的那颗生成树。
最小生成树具有如下性质:
① 任意选择带权连通图的一个顶点作为起点,并加入顶点集合;
② 将还在顶点集合外,但与顶点集合内的顶点邻接的顶点找出,选择与这些顶点相关联的边中,权值最小的一条加入边集合,并将对应的那个顶点加入顶点集合;
③ 重复步骤二,直到顶点集合和边集合构成一个含有n个顶点和n-1条边的极小连通子图,它就是最小生成树。

① 将带权连通图的全部顶点加入顶点集合;
② 在顶点集合内所有未被连通的顶点之间选择权值最小的一条边加入边集合;
③ 重复步骤二,直到顶点集合和边集合构成一个含有n个顶点和n-1条边的极小连通子图,它就是最小生成树。


对于无权图而言,最短路径就是从一个顶点 A 到另一个顶点 B 需经过的边数最少的路径。通常使用广度优先搜索就可以实现:
对于带权图而言,最短路径就是从一个顶点 A 到另一个顶点 B 所经过的边权值之和最小的路径。这类最短路径一般可以分为两类:
我们通常将一条路径开始的顶点称为源点,结束的顶点称为终点,迪杰斯特拉算法生成的就是开始于同一个顶点的单源最短路径。
我们规定只有和某一顶点通过一条边/弧直接连接的顶点才算能够到达的顶点:
① 首先选择一个顶点A作为源点,列表记录源点到各个顶点的距离,其中能够到达的顶点的距离为边/弧的权值,无法到达的顶点的距离为
∞
\infty
∞;
② 选择表中记录的距离最近的一个顶点B加入路径,并且后续表格中不再对源点到该顶点的距离做记录;
③ 计算增加顶点B后,能够到达的顶点与源点间的距离,如果小于之前记录的值,就更新表中关于距离和前置路径的记录;
④ 重复步骤二和三直到计算出源点到各个(可以到达的)顶点的最短距离。

(PS:上图中表格和书上相比,行列互换了:此处行是各个终点,列是已经确定的路径集合;书上相反)
【补充】
- 该算法只要求图必须连通而不一定强连通,所以用于有向图时可能会出现源点与某些顶点间不存在路径的情况;
- 有向图中那些与源点间没有路径的顶点(一般来说没有入度只有出度),就需要以自身为源点计算新的单源最短路径;
- 如果图甚至不连通,那就好比要你开车往返于日本和中国间送货,根本没有路,去不了也回不来;
弗洛伊德算法主要求解带权有向图中任意两顶点间的最短路径。我们同样规定只有和某一顶点通过一条弧直接连接的顶点才算能够到达的顶点:
① 先用一个邻接矩阵记录带权有向图各顶点间的距离,若弧
<
v
i
,
v
j
>
② 在
v
i
v_i
vi和
v
j
v_j
vj之间插入
v
k
v_k
vk,若插入后的路径
(
v
i
,
v
k
,
v
j
)
(v_i,v_k,v_j)
(vi,vk,vj)存在且长度比
A
[
i
]
[
j
]
A[i][j]
A[i][j]要小,则更新
A
[
i
]
[
j
]
A[i][j]
A[i][j]为当前路径长度(保存的路径也要更新);
③ 重复执行步骤二,其中k值按执行次序从0增加到n-1,即步骤二共需执行n次。
用迪杰斯特拉算法求下列有向图的最短路径。

(PS:同样的图中表格和书上相比行列互换了)
图的深度和广度遍历方式常考察算法代码,但还是要了解理论实现方式;
图的遍历是建立生成树(极小连通子图)最可靠的一种办法;
最小生成树是对生成树这一知识点的延伸,主要包含普里姆和克鲁斯卡尔两种算法;
最短路径部分常考察Dijkstra(迪杰斯特拉)算法,需要重点掌握。
首先要明确一点,生成树一定是对于连通图/强连通图而言的,否则不可能存在某一路径将所有顶点连通——即构成生成森林;
而最小生成树就是在所有生成树当中选择一个各边权值之和最小的生成树,因此同样也要求带权图是连通的无向图或强连通的有向图。
而单源最短路径则仅要求带权图必须是连通的无向图或有向图,从某一顶点到各顶点的所有路径中选出权值最小的一条路径,这也就意味着:
所以求解单源最短路径问题时老老实实使用DJ算法;而求解最小生成树问题时除了采用原有的两种方法,也可以尝试DJ算法。
一个无环(没有顶点存在指向自身的弧)的有向图称为有向无环图,简称DAG图。
所谓“拓扑”就是把实体抽象成与形状大小无关的“点”,而把连接实体的关系抽象成“线”。
对一个AOV网进行拓扑排序的一种常用方法:
① 从AOV网中选择一个没有前驱(入度为0)的顶点并输出;
② 从网中删除该顶点和所有以它为起点的有向边;
③ 重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止(后一种情况说明有向图中必然存在环)。
AOV网:若用DAG图表示一个工程,其顶点表示活动,用有向边 < V i , V j >
<Vi,Vj>表示活动 V i V_i Vi必须先于活动 V j V_j Vj进行的这样一种关系,则将这种有向图称为顶点表示活动的网络,记为AOV网。

不同于前面最小生成树和最小路径等问题,拓扑排序不关心各个顶点之间的距离以及顶点本身所代表的实体,它更关心不同的顶点之间是否存在一种拓扑关系——邻接。
拓扑排序主要用于处理有向无环图,如果图中各顶点代表的是各种事务,那么研究顶点之间的“邻接”拓扑关系就是研究各事务间开展的先后顺序:
【分析】拓扑排序所得序列并不唯一。当存在多个入度为0的顶点时不同的选择顺序会产生不同的序列——当有多个优先级相同的事务时,执行事务的先后顺序会产生不同的事务流程。
AOE网:若用带权的DAG图表示一个工程,其顶点表示事件,用有向边(弧)表示活动,弧上的权值表示完成活动的开销(如活动持续时间),则将这种有向图称为用边表示活动的网络,记为AOE网。
AOV网和AOE网虽然都是有向无环图,但它们间有一些区别:
在 AOE 网中仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始;网中也仅存在一个出度为0的顶点,称为结束顶点(汇点),它表示整个工程的结束。
在 AOE 网中,有些活动是可以并行进行的。从源点到汇点的有向路径可能有多条,并且这些路径长度可能不同。完成不同路径上的活动所需的时间虽然不同,但是只有所有路径上的活动都已完成,整个工程才能算结束。
因为关键活动影响了整个工程的时间,如果关键活动不能按时完成,则整个工程的完成时间就会延长,即关键路径长度增加。
所以只要找到了关键活动,就找到了关键路径,也就可以得出最短完成时间。寻找关键活动通常关注如下几个参量:
【补充】
① 活动 < v i , v j ><vi,vj>的最晚开始时间不一定等于事件 v i v_i vi的最晚开始时间,但事件 v i v_i vi的最晚开始时间一定等于与它关联的所有活动 < v i , > <vi,>的最晚开始时间的最小值;
② 活动最晚开始时间不可能小于活动最早开始时间;事件最晚开始时间也不可能小于事件最早开始时间;
③ 当事件 v i v_i vi的最早与最晚开始时间相等时,说明存在某一活动 < v i , v j ><vi,vj>,它的最晚开始时间是最小值且与最早开始时间相等,即活动 < v i , v j > <vi,vj>的最晚开始时间和最早开始时间与事件 v i v_i vi一致。
如果一个活动时间余量为0,说明该活动必须如期完成,否则将拖延整个工程的进程。因此将时间余量为0的活动就是关键活动,由这些活动(带权弧)组成的从源点到汇点的路径就是关键路径。
【★★★】关键路径可能不止一条!!!(详见第七部分习题)
由 补充 - ③ 可知,若活动
<
v
i
,
v
j
>

下列AOE网表示一项包含8个活动的工程。通过同时加快若干活动的进度可以缩短整个工程的工期。下列选项中,加快其进度就可以缩短工程工期的是(C)。

【分析】缩短工期就是指汇点处事件的最早开始时间和最晚开始时间均减小。
- 首先,关键路径上的所有活动都是关键活动,因此可以通过加快关键活动来缩短整个工程的工期。但要注意不能任意缩短,因为缩短到一定程度时,某些关键活动可能会变成非关键活动;
- 其次,网中的关键路径并不唯一,只提高一条关键路径上的关键活动速度并不能缩短工期,并且还可能导致关键路径变为非关键路径。
- 因此这道题目的核心就是找出所有关键路径,并且加快那些包括在所有关键路径上的关键活动,才能达到缩短工程工期的目的。
本题中关键路径有: v 1 → v 3 → v 2 → v 4 → v 6 v_1 \rightarrow v_3 \rightarrow v_2 \rightarrow v_4 \rightarrow v_6 v1→v3→v2→v4→v6 和 v 1 → v 3 → v 2 → v 5 → v 6 v_1 \rightarrow v_3 \rightarrow v_2 \rightarrow v_5 \rightarrow v_6 v1→v3→v2→v5→v6 以及 v 1 → v 3 → v 5 → v 6 v_1 \rightarrow v_3 \rightarrow v_5 \rightarrow v_6 v1→v3→v5→v6,综合选项分析只有 d 和 f 两个关键活动是包含了所有关键路径的。