【DS】7.图

1.简介

一种非线性结构。它的每一个顶点可以和多个其他顶点相关联,各顶点之间的关系是任意的。图是由顶点集合(vertex)及顶点之间的关系集合组成的一种数据结构。Path(x,y)表示从顶点x到顶点y的一条通路,它是有方向的,也称为边(Edge)。

 

2.有关概念

1)有向图与无向图: 在有向图中,<x,y>与<y,x>是不同的两条边,在有向图中(x,y)与(y,x)只是表示顶点x与顶点y之间的一条边,注意各自的记法。

注意:此处不考虑(x,x)或者<x,x>这样的边(自环)

2)完全图: 在n条边组成的无向图中,如果有n(n-1)/2条边,则称之为无向完全图,完全图中边数达到最大;

3)权: 在某些图中,边具有与之相关的数值,称为权重,权重可以表示从一个顶点到另一个顶点的距离,花费的代价等等,这种带权图也称为网络。

4)邻接顶点: 如果(u,v)是E(G)中的一条边,则u与v互为邻接顶点;

5)子图: 设图G=(V,E),G'=(V',E'),且V'属于V,E'属于E,则称图G'是图G的子图;

6)度: 与顶点V关联的边数称为v的度。记作deg(v),在有向图中,顶点的度等于其入度与出度的和;

7)路径: 可以用顶点描述,也可以通过边描述; 路径长度:路径上边的条数,对于带权图,则指的是路径上各边的权值之和;

8)简单路径与回路: 若路径上各个顶点都不重复,则这样的路径称为简单路径;如果路径上的第一个顶点和最后一个顶点重复,则称这样的路径是回路;

9)连通图和连通分量: 在无向图中,若在顶点Vi和Vj之间存在一条从Vi到Vj的路径,则说Vi和Vj之间是连通的,如果图中每一对顶点之间都是连通的,则称此图是连通图。非连通图的极大连通子图称为连通分量;

10)强连通图和强联通分量: 在有向图中,若在每一对顶点Vi和Vj之间都存在一条从Vi到Vj的路径,则称此图是强连通图。非强连通图的极大强连通子图叫做强连通分量;

11)生成树: 一个无向连通图的生成树是它的极小连通子图,若图中含有n个顶点,则其生成树由n-1条边构成,若是有向图,则可能得到它的由若干有向树组成的生成森林

 

3.图的表示存储

1)邻接矩阵: 无向图的邻接矩阵是对称的,有向图的邻接矩阵则不一定是对称的;在表示的时候用一个List表示顶点,用一个矩阵表示边,支持顶点的插入删除,边的插入删除;

缺点:想要知道“图中有多少边”、“图是否连通”等,需要对除了对角线以外的所有n^2-n个元素逐一检查,时间开销很高,另外一方面,当图中的边数e原远远小于n^2时,图的邻接矩阵变成稀疏矩阵,存储利用率很低。

2)邻接表表示: 为了节省空间,将邻接矩阵的各行分别组织为一个单链表。顶点结构中存储顶点的定义以及边链表的头指针,顶点存放在一个分配好的数组里面,边链表里面存放着该顶点的邻接顶点信息,结构为Edge,保存着邻接顶点的定义,权值等信息。无向图的边在表中会出现两次。

有向图则只有一次,此时与顶点i对应的链表所含结点的个数,就是该顶点的出度,因此这种链表又被称为出边表。这样统计一个顶点的出度十分简单,但是统计其入度则变得十分麻烦,需要遍历整个表,为此建立逆邻接表,又称为入边表,链表中存放的是所有进入该顶点的边。

以上两种方法各有优劣,邻接表在无向图中需要存储两遍同一条边,指针也耗费一定的存储空间。

如果在操作中需要在某条边上做标记,则需要做两次,为此,改进方法是采用邻接多重表结构。

 

4.图的遍历

1)定义:给定一个图和其中任意一个顶点V0,从V0出发,沿着图中各边访遍图中的所有顶点,且每个顶点只被访问一次(避免陷入死循环)。 遍历可以被用来寻找图的极大连通子图,也可以被用来消除图中所有的回路等等。

2)解决办法:使用一个标志数组visited[]记录顶点是否被访问过。

3)遍历算法:

a)深度优先遍历(Depth first search, DFS)

深度优先探索是个不断探查和回溯的过程,此处回溯比较简单,因为是否被访问过都已经记录下来;

使用邻接表表示图,则遍历一次需要描过所有的边,即2e,遍历各个结点,即n,所以遍历的复杂性为O(2e+n);如果使用邻接矩阵表示,则查找一个顶点的所有的边,所需的时间为O(n),则遍历所有的顶点所需的时间为O(n^2)

b)广度优先遍历(Breadth first search, BFS)

广度有限搜索是一个逐层遍历的过程,使用队列和递归。

每个顶点进队列一次。如果使用邻接表表示,则该循环的代价是d0+d1+d2+...+dn = O(e),其中di是顶点i的度,总的时间代价为O(n+e),如果使用邻接矩阵,则对于每一个被访问过的顶点,循环要检测矩阵中的n个元素,总的时间代价为O(n^2).

 

对于非连通的无向图,每个连通分量中所有定点的集合和用某种方式遍历它时所走过的边的集合,构成可一棵生成树,这是一个极小连通子图。所有的连通分量的生成树组成了非连通图的生成森林。

 

5.最小生成树

连通图的每一棵生成生成树,都是原图的一个极大无环子图。也就是说,从中删去任何一条边生成树就不再连通,反之在其中引入任何一条新边,都会形成恰好一个回路。按照不同的遍历算法,将得到不同的生成树;从不同的顶点出发,得到的生成树也有所差异。对于一个带权图而言,不同的生成树所对应的总权值也是不一样的,那么如何找出总权值最小的生成树呢?

按照定义:若连通图由n个顶点组成,则其生成树必含有n个顶点,n-1条边。因此,构造最小生成的准则有三条:

1)只能使用该网络中的边来构造最小生成树;

2)只能恰好使用n-1条边来连结网络中的n个顶点;

3)选用的这n-1条边不能构成回路;(满足前两条,这条自能满足)

构造最小生成树的方法有多种,典型的有两种:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的策略,也称为贪心算法。算法描述如下:

在给定的带权图N={V,E}中共有n个顶点,首先构造一个包含全部n个顶点和0条边的森林F={T0,T1...,Tn-1},然后不断迭代。每经过一轮迭代,就会引入一条边。经过n-1轮迭代,最终得到一棵包含n-1条边的最小生成树。需要指出的是:同一带权图可能有多棵最小生成树。

 

Kruskal算法:

算法描述:给定任何一个有n个顶点的连通网络N={V,E},首先构造一个由这n个顶点组成,不含任何边的图T={V,空集},其中每个顶点自成一个连通分量。不断从E中取出权值最小的一条边(若有多条,任取其一),若该边的两个端点来自不同的连通分量(说明不会形成回路),则将此边加入到T中,否则舍弃,如此重复,直到所有的顶点在同一个连通分量上为止。

利用最小堆和并查集来实现Kruskal算法,首先利用最小堆来存放E中所有的边。通过并查集,可以很快的判断任意一条边的两个端顶点是否来自同一个连通分量。

 

Prim算法:

算法描述:

任意给定的带权图N={V,E},算法始终将顶点集合分成两个不重叠的部分,V=Vmst∪(V-Vmst),也就是该图的一个割(cut),其中Vmst是生成树的顶点集合,V-Vmst是图中不在生成树内顶点的集合。这里只考虑连通的网络,所以只要Vmst与V-Vmst均非空,它们之间就至少有一条边相连,这种边称为该割的一座桥。每一轮迭代中,都要在当前割的所有桥中,挑选出权值最小的(u,v),u属于Vmst,v属于V-Vmst,然后将顶点加入到生成树的顶点集合中去,即令Vmst=Vmst∪{v};将边(u,v)加入到生成树的边集合Emst中(注意,如果最短桥有多座该如何处置?)

与Kruskal算法相似,休要一个最小堆存储图的边,每次加入一个新的顶点,就将一个端点在生成树,一个端点不在生成树的边都插入最小堆中,这样就可以得到下一条最短桥,由此不断循环迭代,完成最小生成树的生成。

 (估计其复杂度)

 

6.最短路径

所谓最短路径就是:从在带权图的某一个顶点(称为源点)出发,找出一条通往另一个顶点(称为终点)的最短路径。下面家少三种最常见的最短路径算法:

1)非负权值的单源最短路径

问题的提法是:给定一个有向带权图D与源点v,各边上的权值均为非负,要求找出从v到D中其他各个顶点的最短路径。

算法描述:一般情况下,下一条最短路径总是在“由已经产生的最短路径再扩充一条边”形成的最短路径中得到。假设S是已经求得的最短路径的终点的集合,则可以证明:下一条最短路径必然是从V0出发,中间经过S中的顶点再扩充一条边便可到达顶点Vi的各条路径中的最短者。为了当前找到的从源点V0到其他顶点的最短路径长度,需要引入一个辅助数组dist[],它的每一个分量dist[i]表示从当前找到的从源点V0到重点Vi的最短路径的长度,它的初始值是正无穷。

2)任意权值的单源最短路径

这个算法有个缺陷,就是不能够有负权值的路径组成的回路。

 

posted @ 2012-08-10 22:26  大脚印  阅读(475)  评论(0)    收藏  举报