第九章      解决图的编程问题

图的定义:

         图是由一系列定点(结点)和描述定点之间的关系边(弧)组成。图是数据元素的集合,这些数据元素被相互连接以形成网络。其形式化定义为:

G=VE

V={Vi|Vi∈某个数据元素集合)}

E={ViVj|Vi+VjV^P(Vi,Vj)}

         其中,G表示图;V是顶点集合;E是边或弧的集合。在集合E中,PViVj)表示顶点Vi和顶点Vj之间有边或弧相连

相关术语

         顶点集:图中具有相同特性的数据元素的集合成为顶点集

         边(弧):边是一对顶点间的路径,通常带箭头的边称为弧

         弧头:每条箭头线的头顶点表示构成弧的有序对中的后一个顶点,称为弧头或终点

弧尾:每条箭头线的尾顶点表示成弧的有序对中的前一个顶点,称为弧尾或始点

度:在无向图中的顶点的度是指与那个顶点相连的边的数量

入度:顶点的入度是指向那个顶点的边的数量

出度:顶点的出度是由那个顶点出发的边的数量

权:有些图的边(弧)附带一些数据信息,这些数据信息称为边(或弧)的权

图的分类

         有向图,无向图,有向完全图,无向完全图,稠密图,稀疏图

邻接矩阵

         一种通用的图的存储结构,是由两个数组来表示图,一个数组是一维数组,存储图中的顶点信息,一个数组是二维数组,即矩阵,存储顶点之间相邻的信息,也就是边(弧)的信息

邻接表

         邻接表的存储方法是一种顺序存储与链式存储相结合的存储方法,顺序存储部分用来保存图中顶点信息,链式存储部分用来保存图中边(或弧)的信息。具体的做法是:对于图中的顶点,使用一个一维数组,其中每个数组元素包含两个域,其结构为:

wKioL1Ql-lDSH_-2AAAY4u_3vSg936.jpg

其中,

         顶点域(data):存放与顶点有关的信息

         头指针域(firstadj):存放与该结点相邻接的所有顶点组成的单链表的头指针。

邻接单链表中每个结点表示依附于该顶点的一条边,称为边结点,边结点结构为:

wKiom1Ql-irQ62qUAAAk7U4Cai8032.jpg

其中,

         邻接点域(adjvex):指示与顶点邻接点在图中的位置,对应着一维数组中的序号,对于有向图,存放的是该边结点所表示的弧的弧头顶点在一维数组中的序号

         数据域(info):存储边或弧相关的信息,如权值等

         链域(nextadj):指向依附于该顶点的下一个边结点的指针

图的遍历

  1. 深度优先搜索算法:

从图的某一顶点出发,访问x,然后遍历任何一个与x相邻的违背访问的顶点y,再遍历任何一个与y相邻的未被访问的顶点z……如此下去,直到到达一个所有邻接点都被访问的顶点为止。然后依次退回到尚有邻接点未被访问过的顶点,重复上述过程,直到图中的全部顶点都被访问过为止。

深度优先搜索背后基于堆栈。

深度优先搜索,对下图所示的示例:

wKioL1Ql-lDwWuOoAABOn1BhZZ4422.jpg

1)  将起点V1压入栈

2)  将顶点元素V1弹出栈,访问它,将与V1相邻的未被访问的所有顶点元素V4V2压入栈

3)  从栈中弹出顶上的元素V2,访问它。将与V2相邻的未被访问所有顶点元素V6V3压入栈

4)  从栈中弹出顶层元素V2,访问它。将与V3相邻的所有未被访问的元素V6压入栈

5)  从栈中弹出顶层元素V5,访问它。在栈中压入所有它的未访问的临接顶点,点V5没有任何未被访问的邻接点,因此没有顶点被入栈中

6)  从栈中弹出顶层元素V6访问它。在栈中压入所有它的未访问的邻接点,顶点V6没有任何被访问的邻接点,因此没有顶点被入栈中

7)  从栈中弹出顶层元素V4,访问它。在栈中压入所有它的未被访问的邻接顶点。顶点V4没有任何未被访问的邻接顶点,因此没有顶点被入栈中,在V4弹出后,栈中的内容是空的,因此,遍历完成

  1. 广度优先搜索

图的广度优先搜索是从图的某个顶点x出发,访问x。然后访问与x相邻接的所有未被访问的顶点x1x2……xn相邻的未被访问过的所有顶点。以此类推,直至图中的每个顶点都被访问。

对示例进行广度优先搜索如下:

1)  从第一个顶点V1开始遍历。在访问了顶点V1之后,访问与顶点V1邻接的所有顶点。与V1邻接的顶点有V2V4.可以以任何顺序访问顶点V2V4,假设先访问顶点V2,再访问顶点V4

2)  先遍历与V2邻接的所有未被访问的顶点,与V2邻接的未被访问的顶点是V3V6,先访问V3再访问V6;然后访问与V4邻接的顶点,与V4邻接的未被访问的顶点是V5

3)  依次遍历与顶点V3V6V5邻接的未被访问的顶点,没有与V3V6V5相邻接的未被访问的顶点所有的顶点都被遍历了

图的最短路径(Dijkstra算法)

         基本思想:设置两个顶点集合TS,集合S中存放已经找到最短路径的顶点,集合T中存放当前还未找到最短路径的顶点。初始状态时,集合S中只包含源点v0,然后不断从集合T中选取到源点v0路径最短的顶点w加入集合S,集合S中每加入一个新的结点w,都要修改顶点v0到集合T中剩余顶点的最短路径长度值,集合T中各顶点新的最短路径长度值为原来最短路径长度值与顶点w的最短路径长度加上w到该顶点的路径长度值中的较小值。此过程不断重复,直到集合T的顶点全部加入到集合S中为止。

下面用下图给出Dijstra算法的具体实现

采用邻接矩阵作为存储结构:


 Vij  顶点ij之间的权值

matrix[i,j]= 0   i=j顶点ij是同一个顶点

   顶点ij之间没有边

wKioL1Ql-pCAF9LFAACLvXMCzMQ641.jpg

设置一个一维数组final来标记已找到最短路径的顶点,并规定:

wKiom1Ql-mmSwxyDAABFA27mpsQ550.jpg

除了final数组外,还需要另一个数组distance,它用来存储从A到其他城市的距离。距离可以使直接的也可以间接的

如果从城市A出发,对于上图,数组distancefinal将被初始化为:

distance={090,∞,150,∞,180}

final={1,0,0,0,0,0}

下面详细描述Dijkstra算法来确定从城市A到其他城市的最短距离。步骤如下:

1.选择数组distance中具有最短路径的顶点v,使得:

distance[v]=min{distance(w)}(s[w]=0)

然后将v加入集合final中,即令final[w]=1

distance数组中具有最小距离的顶点A(距离是0)。但该结点已经在final数组中标记为1,因此,选择对应于下一个最小距离的顶点,也就是顶点B,距离是90,并将其对应的final值设为1,如图1所示:

wKioL1Ql-pCQ2RsQAADT22kfoh4693.jpg

2.对于所有final[i]=0的顶点wi,判断distance[i]是否小于distance[v]+matrix[vi],如果不是,则使得:

distance[i]= distance(i)}+matrix[v,i]

这里,v=1,并且distance[1]=90,现在考虑所有不在final中的顶点

  • w=2:从A途径BC的距离是:90+60=150,它小于已经记录的距离distance[2](),因此distance[2]改为150

  • w=3:A途径BD距离的距离是:90+50=140,它小于已经记录的距离150,因此distance[3]改为140

  • w=4:从A途径B到到E的距离是:90+=∞,distance[4]保持不变

  • w=5:从A途径BF的距离是:90+=∞,它大于已经记录的距离180distance[5]保持不变

如图2所示

  1. 选择数组distance中具有最短路径的顶点v,使得:

distance[v]=min{distance(w)}(s[w]=0)

然后将v加入到集合final中,即令final[w]=1

distance数组中没有在final数组中标记为1的具有最小距离的顶点是D距离是140,并将其对应的final设为1,如图3所示

4. 对于所有final[i]=0的顶点wi,判断distance[i]是否小于distance[v]+matrix[vi],如果不是,则使得:

distance[i]= distance(i)}+matrix[v,i]

         这里,v=3,并且distance[3]=140,现在考虑所有不在final中的顶点

  • w=2:从A途径DC的距离是:90+=∞,因此distance[2]不变

  • w=4:从A途径D到到E的距离是:140+110=250,它小于已经记录的距离∞因此distance[4]改为250

  • w=5:从A途径DF的距离是:90+280=420,它大于已经记录的距离180distance[5]保持不变

如图4所示

5 选择数组distance中具有最短路径的顶点v,使得:

distance[v]=min{distance(w)}(s[w]=0)

然后将v加入到集合final中,即令final[w]=1

distance数组中没有在final数组中标记为1的具有最小距离的顶点是C距离是150,并将其对应的final设为1,如图5所示

wKiom1Ql-mnjWTWgAAC-PP1dyBs750.jpg

6. 对于所有final[i]=0的顶点wi,判断distance[i]是否小于distance[v]+matrix[vi],如果不是,则使得:

distance[i]= distance(i)}+matrix[v,i]

         这里,v=2,并且distance[3]=140,现在考虑所有不在final中的顶点

  • w=4:从A途径C到到E的距离是:150+230=380,它大于已经记录的距离250distance[4]保持不变

  • w=5:从A途径CF的距离是:150+80=230,它大于已经记录的距离180distance[5]保持不变

如图6所示

7. 选择数组distance中具有最短路径的顶点v,使得:

distance[v]=min{distance(w)}(s[w]=0)

然后将v加入到集合final中,即令final[w]=1

distance数组中没有在final数组中标记为1的具有最小距离的顶点是F 距离是180,并将其对应的final设为1,如图7所示

wKiom1Ql-mnSVBaIAAC60o3m-II438.jpg

8. 对于所有final[i]=0的顶点wi,判断distance[i]是否小于distance[v]+matrix[vi],如果不是,则使得:

distance[i]= distance(i)}+matrix[v,i]

         这里,v=5,并且distance[5]=180,现在考虑所有不在final中的顶点

  • w=4:从A途径F到到E的距离是:180+30=210,小于已经记录的距离250因此distance[4]改为210

如图8所示

9. 选择数组distance中具有最短路径的顶点v,使得:

distance[v]=min{distance(w)}(s[w]=0)

然后将v加入到集合final中,即令final[w]=1

distance数组中没有在final数组中标记为1的具有最小距离的顶点是E距离是210,并将其对应的final设为1

现在所有顶点都在final数组中被标记为1.这样distance数组存放的就是从源A到其他顶点的最短路径,如图9所示