数据结构与算法——图
图(graph)是一种比线性表、树更为复杂的数据结构。在线性表中,数据元素之间呈线性关系,即每个元素只有一个直接前驱和一个直接后继。在树型结构中,数据元素之间有明显的的层次关系,即每个结点只有一个直接前驱,但可有多个直接后继,而在图结构中,每个结点即可有多个直接前驱,也可有多个直接后继,因此,树结构是图结构的一种特殊情形。当一个树结构中允许同一结点出现在不同分支上时,该树结构实际上就是一个图结构。图的最早应用可以追溯到十八世纪数学家欧拉(EULer)利用图解决了著名的哥尼斯堡桥的问题,为图在现代科学技术领域的应用奠定了基础。
一、图的基本概念
1、图的定义
图是一种数据结构,图和树一样可以用二元组表示。它可定义为Graph=(V,R)其中,V={x|x∈datatype},R={VR},VR={<x,y>|P(x,y)∧(x,y∈V)}。在图中,数据元素常称为顶点(Vertex),V是顶点的非空有穷集合;R是边(弧)的有穷集合。VR是两个顶点之间的关系集合。顶点之间关系可用序偶对来表示。若<x,y>∈VR,则〈x,y>表示从x到y有一条弧(arc,或称又向边),且称x为弧尾(tail)或初始点(initial node),称y为弧头(Read)或终端点(terminal node),此时的图称为有向图(digraph)。若<x,y>∈VR,必有<y,x>∈VR即VR是对称的,以无序对(x,y)代替这两个有序对,表示x和y之间的一条边(edge),此时的图称为无向图(undigraph)。谓词P(x,y)表示从x到y有单向通路或其他信息。从逻辑上看,图是由顶点和边组成,边反映出顶点之间的联系。
2、图的基本术语
不考虑结点的自返圈,即结点到其自身的边,若〈V1,V2〉或〈V1,V2〉是图的一条边,则V1≠V2,并且也不允许一条边在图中重复出现。
(1)完全图
在一个有n个顶点的图中,若每个顶点到其他(n-1)个顶点都连有一条边,则图中有n个顶点且有n*(n-1)/2条边的图称为完全图。任一个具有n个顶点的有向图,其最大边数为n*(n-1).
(2)邻接点、相关边
对于无向图G=(V,E),若(V1,V2)∈E,则称V1和V2互为邻接点(adjacent),即V1和V2相邻接,而边(V1,V2)则是与结点V1和V2“相关联的边”。在有向图G=(V,A)中,若<V1,V2>∈A,则称结点V1邻接到结点V2,结点V2邻接于V1,而边〈V1,V2〉是与结点V1,V2相关联的。
(3)顶点的度、入度、出度
顶点的度(degree)是和V相关联的边的数目,计为TD(V).在有向图G=(V,A)中,如果弧<V1,V2>∈A,则以V1为头的弧的数目称为V1的入度(indegree),记为ID(V1);以V1为尾的弧的数目称为V1的出度(outdegree),,记为OD(V1);顶点的度为TD(V1)=ID(V1)+OD(V1).
(4)路径、回路
无向图G=(V,E)中,从顶点V到顶点V’的路径(Path)是一个顶点序列(V=Vi0,Vi1,Vi2,……,Vim=V’),其中(Vij-1,Vij)∈E,1<=j<=m.如果是有向图,则路径也是有向的,顶点序列满足(Vij-1,Vij)∈E,1<=j<=n,路径长度是路径上的边或弧的数目。第一个顶点和最后一个顶点相同的路径称为回路或环(cycle),序列中顶点不重复的称为简单路径。除了第一个顶点和最后一个顶点外,其余顶点不重复的回路,称为简单回路或简单环。
(5)子图
假设有两个图G={V,{E}}和G’={V’,{E’}},如果V’包含于V,E’包含于E,则称G’是G的子图。
(6)连通和强连通
在无向图G中,如果从顶点V到顶点V’有路径,则称V和V’是连通的。如果对于图G中任意两个顶点Vi,Vj ∈V都是连通的,则称为G是连通图(connected graph).连通分量指的是无向图中极大连通子图。在有向图G中,如果对于每一对Vi,Vj∈V,Vi<>Vj,从Vi到Vj和从Vj到Vi都存在路径,则称G是强连通图。有向图中极大强连通子图称作为有向图G的强连通分量。
(7)生成树
一个连通图的生成树,它含有图中全部顶点,但只有足以构成一棵树的n-1条边。
(8)权、网
在图的边或弧上,有时标有与它们相关的数,这种与图的边或弧相关的数称作权(Weight)。这些权可以表示从一个顶点到另一个顶点的距离或代价。这种带权的图常称作网(network)。
二、图的存储结构
1、邻接矩阵表示法
邻接矩阵是表示顶点之间相邻关系的矩阵。设G=(V,E)是具有n个顶点的图,由G的邻接矩阵是具有如下性质的n阶方阵其定义为:
1 若<i,j>或<j,i>∈E
A[i,j]=
0 反之
这一n*n的方阵,可借助二维数组作为存储结构。将邻接矩阵中的0,1还成权值,就是图的邻接矩阵。无向图的邻接矩阵是对称矩阵,顶点vi的度是邻接矩阵中第i行(或第i列)的元素1之和。有向图的邻接矩阵不一定是对称矩阵;顶点vi的出度是邻接矩阵中第i行元素之和,入度是邻接矩阵中第i列的元素之和。用邻接矩阵表示有向图所需的存储空间为n*n位。对无向图,由于其对称性,仅需存入下三角(或上三角)的元素,故有n个顶点的无向图仅需n(n+1)/2存储空间。用邻接矩阵表示有n个顶点的图,测试其边的数目时,必须按行、列逐次测试,故需O(n*n)次。通过邻接矩阵可容易判定顶点间有无边(弧),容易计算顶点的度(出度、入度);缺点是所占用空间只和顶点个数有关,和边数无关,在边数较少时,空间浪费较大。一般在顶点数较少且边数稠密时应用邻接矩阵。
2、邻接表
邻接表是为了克服邻接矩阵在图为稀疏图时的空间浪费大的这个缺点而提出的。邻接表是顶点的向量结构和边(弧)的单链表结构的集合,每个顶点结点包括两个域,将n个顶点放在一个向量中(成为顺序存储的结点表);一个顶点的所有邻接点链结成单链表,该顶点在向量中有一个指针域指向其第一个邻接点。邻接表结构: 顶点结点:vexdata | frist adjvex | info | next
其中,vexdata是顶点数据,firstarc是指向该顶点第一个邻接点的指针,adjvex是邻接点在向量表中的下标,info是邻接点的信息,next是指向下一邻接点的指针。
对无向图,容易求各顶点的度;边表中结点个数是边数的两倍。对有向图,容易求顶点的出度;若求顶点的入度则不容易,要遍历整个表。为了求顶点的入度,有时可设逆邻接表(指向某顶点的邻接点链接成单链表)。所谓逆邻接表就是对图中的每个顶点i建立一个单链表,把被i邻接的顶点放在一个链表中,即边表中存放的是入度边而不是出度边。一般在处理稀疏矩阵时用邻接表。
构建一个图的邻接表
struct node /*单链表中结点结构 */
{
int vertex; /*顶点编号*/
struct node * next /*指针域*/
};
struct headnode /*数组中元素的结构*/
{
int vert; /*顶点编号*/
struct node * link /*指针域*/
};
为了方便,先给出一个将顶点b加入到顶点a的邻接链表的函数:
struct headnode * linkup (a, b, head);
int a, b;
struct headnode * head;
{
struct node * p;
while(head-> vertex! = a) /*找到编号为a的顶点*/
{
head = head+1;
p = head-> link;
}
while( (p! =NULL) && ( p-> vertex ! =b ))
p =p->next /*查编号为b的顶点是否为a的链接顶点*/
if (p = =NULL) /*如果未查到*/
{
p = (struct node * )malloc(sizeof(struct node)); // 开辟新节点
p-> vertex = b; /*将b放入*/
p-> next = head->link; /*指针重新定向*/
head-> link =p; /*a指向b*/
return(head);
}
}
构造邻接表的函数为:
struct headnode *adjlist(d, n)
int n;
int d[ ];
{
struct headnode head[100] ;
struct node * q, * p ;
int i,vl;
for(i=0; i<n; i++ )
{
head[i].vert=d[i]; /*为每个顶点建立一个连接*/
head[i].link=NULL; /*下一个指针先置空*/
printf (“inputlinked list of \ n”); /*输入和此顶点相连的顶点*/
scanf (“%d”, &vl);
while (vl> =0) /*若此数值为有效/*
{
p= (struct node * ) malloc(sizeof(struct node)); /*开辟新节点*/
p->vertex=vl; /*放入顶点编号*/
p->next = ead[i].link;/*重新将指针定向*/
head[i].link=p;
scanf(“%d”, &vl); /*再输入下一个相连的顶点编号*/
}
}
return (head);
}
以上函数返回值为邻接表的首地址。
3、十字链表
十字链表(orthogonallist)是有向图的一种链式存储结构,可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中对应于有向图中的每一条弧和每个顶点的结构如下:
顶点结点:data | firstin| firstout
弧结点: tailvex |headvexz | hlink | tlink
在顶点结点中有三个域:data域存储和顶点相关的信息,如顶点名称等;firstin和firstout为两个链域,分别指向以该结点为弧头和弧尾的第一个结点。为了存取方便,顶点一般采用顺序存储方式,存储在一维数组中。在弧结点中有四个域:尾域(tailvex)和头域(headvex)分别表示弧尾和弧头这两个顶点在图中的的编号,链域hlink指向弧头相同的下一条弧,而链域tlink指向弧尾相同的下一条弧。若加上与弧相关的其他信息,则需要定义更多的域空间。在十字链表中,既容易找到以Vi为头的弧,也容易找到以Vi为尾的弧,因而容易求得顶点Vi的入度和出度。
4、邻接多重图
邻接多重图(adjacencymultlist)是无向图的一种存储结构。在邻接表中每一条边的两个结点(Vi,Vj)分别在第i个和第j个链表之中,这给图的某些操作带来不便。例如在图的应用中需要对已被搜索过的边作记号或对一条边进行操作入删除等等。此时,需要找到表示同一条边的两个顶点,因此,对无向图进行这类操作时,采用邻接多重表作为存储结构更为适宜。在邻接多重表中,图的每条边只用一个结点表示。
其结点结构如下:mark | ivex |ilink | jvex | jlink
其中,mark为标志域,用以标记该条边是否已被访问;ivex和jvex为该边依附的两个顶点;ilink和jlink分别为指向依附于ivex和jvex下一条边的指针。
每个顶点也可以用一个结点表示:data |firstedage
data域存储和顶点有关的信息,firstedage指示第一条依附于该定点的边。
在邻接多重表中,所有依附于同一顶点边都串联在同一链表中,由于每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。
5、边集数组
带权图的另一种存储结构是边集数组,用边集数组表示带权图时列出每条边的起、始顶点及依附于这两个顶点的边上的权,边集数组一般可用一个二维数组分别存储依附于每条边的两端点,边上的权值用一个一维数组存储。在边集数组表示中,数组中对应的列下标表示同一弧的顶点及权值。它适用于一些以边为主的操作。这一存储结构同样适用于无向图。
转载自:http://blog.youkuaiyun.com/mi6236/article/details/8115152