数据结构之图

一、图的定义

1、图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

2、线性表中把数据元素叫元素,树中将数据元素叫结点,在图中数据元素则称之为顶点。

3、线性表中可以没有数据元素,称为空表;树中可以没有结点,叫做空树;在图结构中,不允许没有顶点,在定义中,若V是顶点的集合,则强调了顶点集合V有穷非空。(注意:此处定义有争议,国内部分教材强调点集非空,但在http://en.wikipedia.org/wiki/Null_graph中提出点集可为空)

4、在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

5、若顶点vi到vj之间的边没有方向,则称这条边为无向边,用无序偶对(vi,vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。如下图就是一个无向图,由于是无方向的,所以连接顶点A与D的边,既可以表示成无序对(A,D),也可以表示成(D,A)。

数据结构之图_第1张图片

6、若从顶点vi到vj之间的边有方向,则称这条边为有向边,也称为弧,用有序偶<vi,vj>来表示,其中vi称为弧尾,vj称为弧头。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。下图就是一个有向图,其中<A,D>就表示弧,注意不能写成<D,A>。

数据结构之图_第2张图片

7、在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。下面的两个图都不是简单图。

数据结构之图_第3张图片

8、在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。含有n个顶点的无向完全图有n x (n-1) / 2条边。如下图。

数据结构之图_第4张图片

9、在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n x (n-1)条边。如下图。

数据结构之图_第5张图片

10、对于具有n个顶点和e条边数的图,无向图0<=e<=n(n-1)/2,有向图0<=e<=n(n-1)。

11、有很少条边或弧的图称为稀疏图,反之称为稠密图。这里的稀疏和稠密都是相对而言的。

12、有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权。这种带权的图通常称为网。如下图。

数据结构之图_第6张图片

13、假设有两个图G1=(V1,{E1})和G2=(V2,{E2}),如果V2⊆V1且E2⊆E1,则称G2为G1的子图。如下带底纹的图均为左侧无向图与有向图的子图。

数据结构之图_第7张图片

二、图的顶点与边间关系

1、对于无向图G=(V,{E}),如果边(v1,v2)∈E,则称顶点v1和v2互为邻接点,即v1和v2相邻接。边(v1,v2)依附于顶点v1和v2,或者说(v1,v2)与顶点v1与v2相关联。顶点v1的度是和v1相关联的边的数目,记为TD(v1)。如下图顶点A与B互为邻接点,顶点A的度是3,各个顶点度的和为3+2+3+2=10,此图的边数是5,发现,边数其实就是各顶点度数和的一半,多出的一半是因为重复两次记数。简记为

数据结构之图_第8张图片

2、对于有向图G=(V,{E}),如果弧<v1,v2>∈E,则称顶点v1邻接到顶点v2,顶点v2邻接自顶点v1。弧<v1,v2>和顶点v1,v2相关联。以顶点v1为头的弧的数目称为v1的入度,记为ID(v1);以v1为尾的弧的数目称为v1的出度,记为OD(v1);顶点v1的度为TD(v1)=ID(v1)+OD(v1)。如下图,顶点A的入度为2,出度为1,所以顶点A的度为2+1=3;此有向图的弧有4条,各顶点的出度和为1+2+1+0=4,各顶点的入度和为2+0+1+1=4,所以得到

数据结构之图_第9张图片

3、无向图G=(V,{E})中从顶点v1到顶点v2的路径是一个顶点序列(v1=vi,0,vi,1,......,vi,m=v2),其中(vi,j-1,vi,j)∈E,1≤j≤m。如下图的左图的顶点B到顶点D的四种不同的路径。如果G是有向图,则路径也是有向的,顶点序列应该满足<vi,j-1,vi,j>∈E,1≤j≤m。如下图的右图顶点B到顶点D有两种路径,而顶点A到B就不存在路径。

                                                     数据结构之图_第10张图片

4、路径的长度是路径上的边或弧的数目。如上图的右图的路径长度分别是2和3。

5、第一个顶点到最后一个顶点相同的路径称为回路或环(路径最终回到起始点)。序列中顶点不重复出现的路径称为简单路径。除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环。如下图的粗线部分都构成环,左侧的环因第一个顶点和最后一个顶点都是B,而C、D、A没有重复出现,所以是一个简单环。而右侧的环由于顶点C的重复就不是简单环了。

数据结构之图_第11张图片

三、连通图相关术语

1、在无向图G中,如果从顶点v1到顶点v2有路径,则称v1和v2是连通的。如果对于图中任意两个顶点vi、vj∈V,vi和vj都是连通的,则称G是连通图。下图的左图是无向非连通图,而右图是无向连通图。

数据结构之图_第12张图片

2、无向图中的极大连通子图称为连通分量。强调:

     1)要是子图。

     2)子图要是连通的。

     3)连通子图含有极大顶点数。

     4)具有极大顶点数的连通子图包含依附于这些顶点的所有边。   上图中左图虽然不是连通图,但是它有两个连通分量,即上图中的右图和下图中的左图,因为下图中的右图不满足连通子图的极大顶点数,所以不是上图中左图的连通分量。

数据结构之图_第13张图片

3、在有向图G中,如果对于每一对vi、vj∈V,vi≠vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量。

4、一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。如下图的图1不是生成树,当去掉两条构成环的边后,比如图2或图3,就满足n个顶点,n-1条边且连通的定义,图2和图3都是一棵生成树。所以:如果一个图有n个顶点和小于n-1条边,则是非连通图,如果多于n-1条边,则必定构成一个环,因为这条边使得它依附的那两个顶点之间有了第二条路径,比如图2和图3,随便加哪两顶点的边都将构成环。不过有n-1条边并不一定是生成树,比如图4。

数据结构之图_第14张图片

5、如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1,则是一棵有向。所谓入度为0其实就相当于树中的根结点,其余顶点入度为1就是说树的非根结点的双亲只有一个。一个有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。如下图所示,其中图1是一个有向图,去掉一些弧后,分解为两棵有向树,如图2和图3,且这两棵有向树就是图1有向图的生成森林。

数据结构之图_第15张图片

四、图的存储结构之邻接矩阵

1、从图的逻辑结构定义来看,图上任何一个顶点都可被看成是第一个顶点,任一顶点的邻接点之间也不存在次序关系。如下面的四张图,它们其实是同一个图,只是顶点的位置不同。

数据结构之图_第16张图片

2、图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

3、设图G有n个顶点,则邻接矩阵是一个n x n的方阵,定义为:

     

     无向图邻接矩阵实例,如下图,可以发现无向图的边数组是一个对称矩阵。

     数据结构之图_第17张图片

     有向图邻接矩阵实例,如下图,有向图的边数组就不是对称的了,还可以发现顶点v1的入度正好是第v1列各数之和;而出度即是该行的各数之和。

     数据结构之图_第18张图片

4、设图G是网图,有n个顶点,则邻接矩阵是一个n x n的方阵,定义如下,其中Wij表示(vi,vj)或<vi,vj>上的权值。∞表示一个计算机允许的、大于所有边上权值的值,即一个不可能的极限值。

     数据结构之图_第19张图片

     有向网图邻接矩阵实例,如下图

     数据结构之图_第20张图片

5、图的邻接矩阵存储的结构代码如下:

[cpp]  view plain copy print ?
  1. #define MAXVEX 100           /* 最大顶点数 */  
  2. #define INFINITY 65535       /* 用65535来代表∞ */  
  3.   
  4. typedef char VertexType;     /* 顶点类型  */  
  5. typedef int EdgeType;        /* 边上的权值类型 */  
  6.   
  7. typedef struct  
  8. {  
  9.     VertexType vexs[MAXVEX];           /* 顶点表 */  
  10.     EdgeType arc[MAXVEX][MAXVEX];      /* 邻接矩阵,可看作边表 */  
  11.     int numNodes, numEdges;            /* 图中当前的顶点数和边数  */  
  12. }MGraph;  

6、建立无向网图的邻接矩阵表示的代码如下,从中可以得到n个顶点和e条边的无向网图的创建,时间复杂度为O(n+n²+e),其中对邻接矩阵Garc的初始化消耗了O(n²)的时间。

[cpp]  view plain copy print ?
  1. /* 建立无向网图的邻接矩阵表示 */  
  2. void CreateMGraph(MGraph *G)  
  3. {  
  4.     int i, j, k, w;  
  5.     printf("输入顶点数和边数:\n");  
  6.     scanf("%d,%d", &G->numNodes, &G->numEdges);              /* 输入顶点数和边数 */  
  7.     for(i = 0; i < G->numNodes; i++)                         /* 读入顶点信息,建立顶点表 */  
  8.     {  
  9.         scanf("%c", &G->vexs[i]);  
  10.     }     
  11.     for(i = 0; i < G->numNodes; i++)  
  12.     {  
  13.         for(j = 0; j < G->numNodes; j++)  
  14.         {  
  15.             G->arc[i][j] = INFINITY;                      /* 邻接矩阵初始化 */  
  16.         }     
  17.     }     
  18.     for(k = 0; k < G->numEdges; k++)                         /* 读入numEdges条边,建立邻接矩阵 */  
  19.     {  
  20.         printf("输入边(vi,vj)上的下标i,下标j和权w:\n");  
  21.         scanf("%d,%d,%d", &i, &j, &w);                       /* 输入边(vi,vj)上的权w */  
  22.         G->arc[i][j] = w;   
  23.         G->arc[j][i] = G->arc[i][j];                         /* 因为是无向图,矩阵对称 */  
  24.     }  
  25. }  

7、对于边数相对顶点较少的图,这种结构是存在对存储空间的极大浪费的。如下图。

数据结构之图_第21张图片

五、图的存储结构之邻接表
1、 数组与链表相结合的存储方法称为邻接表。邻接表的处理办法:

      1)图中顶点用一个一维数组存储(也可以用单链表来存储),对于顶点数组中,每个数据元素还要存储指向第一个邻接点的指针。

      2)图中每个顶点vi的所有邻接点构成一个线性表,由于其个数不定,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。

2、无向图的邻接表结构图示:其中顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点;边表结点由adjvex和next两个域组成,adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

数据结构之图_第22张图片

3、如下图所示,是有向图的邻接表和逆邻接表,要注意的是有向图由于有方向, 是以顶点为弧尾来存储出边表的,这有利于得到每个顶点的出度。为了便于确定顶点的入度或以顶点为弧头的弧,可以建立一个有向图的逆邻接表,即对每个顶点vi都建立一个链接为vi为弧头的表。

数据结构之图_第23张图片

4、对于带权值的网图,可以在出边表结点定义中再增加一个weight的数据域,存储权值信息即可,如下图所示。

数据结构之图_第24张图片

5、邻接表结点定义代码如下:

[cpp]  view plain copy print ?
  1. #define MAXVEX 100                   /* 最大顶点数*/  
  2.   
  3. typedef char VertexType;             /* 顶点类型 */  
  4. typedef int EdgeType;                /* 边上的权值类型 */  
  5.   
  6. /* 边表结点  */  
  7. typedef struct EdgeNode   
  8. {  
  9.     int adjvex;                      /* 邻接点域,存储该顶点对应的下标 */  
  10.     EdgeType weight;                     /* 用于存储权值,对于非网图可以不需要 */  
  11.     struct EdgeNode *next;           /* 链域,指向下一个邻接点 */  
  12. }EdgeNode;  
  13.   
  14. /* 顶点表结点 */  
  15. typedef struct VertexNode   
  16. {  
  17.     VertexType data;                 /* 顶点域,存储顶点信息 */  
  18.     EdgeNode *firstedge;             /* 边表头指针 */  
  19. }VertexNode, AdjList[MAXVEX];  
  20.   
  21. typedef struct  
  22. {  
  23.     AdjList adjList;   
  24.     int numNodes, numEdges;           /* 图中当前顶点数和边数 */  
  25. }GraphAdjList;  

6、无向图的邻接表的创建代码:其中应用了单链表创建中的头插法,对于n个顶点和e条边来说,时间复杂度是O(n+e)。

[cpp]  view plain copy print ?
  1. /* 建立图的邻接表结构 */  
  2. void  CreateALGraph(GraphAdjList *G)  
  3. {  
  4.     int i, j, k;  
  5.     EdgeNode *e;  
  6.     printf("输入顶点数和边数:");  
  7.     scanf("%d,%d", &G->numNodes, &G->numEdges);          /* 输入顶点数和边数 */  
  8.     for(i = 0; i < G->numNodes; i++)                     /* 读入顶点信息,建立顶点表 */  
  9.     {  
  10.         scanf("%c", &G->adjList[i].data);                 /* 输入顶点信息 */  
  11.         G->adjList[i].firstedge = NULL;               /* 将边表置为空表 */  
  12.     }  
  13.       
  14.     for(k = 0; k < G->numEdges; k++)                     /* 建立边表 */  
  15.     {  
  16.         printf("输入边(vi,vj)上的顶点序号:");  
  17.         scanf("%d,%d", &i, &j);                          /* 输入边(vi,vj)上的顶点序号 */  
  18.         e = (EdgeNode *)malloc(sizeof(EdgeNode));        /* 向内存申请空间,生成边表结点 */  
  19.         e->adjvex = j;                                    /* 邻接序号为j */                           
  20.         e->next = G->adjList[i].firstedge;                 /* 将e的指针指向当前顶点上指向的结点 */  
  21.         G->adjList[i].firstedge = e;                  /* 将当前顶点的指针指向e */                 
  22.           
  23.         e = (EdgeNode *)malloc(sizeof(EdgeNode));        /* 向内存申请空间,生成边表结点 */  
  24.         e->adjvex = i;                                    /* 邻接序号为i */                           
  25.         e->next = G->adjList[j].firstedge;                 /* 将e的指针指向当前顶点上指向的结点 */  
  26.         G->adjList[j].firstedge = e;                  /* 将当前顶点的指针指向e */                 
  27.     }  
  28. }  

六、图的存储结构之十字链表

1、对于有向图来说,邻接表是有缺陷的,它解决了出度却不容易了解入度,反之逆邻接表解决了入度却不容易了解出度,把邻接表和逆邻接表整合在一起,即成了有向图的一种存储方法:十字链表

2、重新定义顶点表结点结构如下图,其中firstin表示入边表头指针,指向该顶点的入边表中第一个结点;firstout表示出边表头指针,指向该顶点的出边表中的第一个结点。

     重新定义的边表结点结构如下图,其中tailvex是指弧起点在顶点表的下标,headvex是指弧终点在顶点表中的下标,headlink是指入边表指针域,指向终点相同的下一条边;taillink是指出边表指针域,指向起点相同的下一条边,如果是网,可以再增加一个weight域来存储权值。

3、如下图是十字链表的存储图示,其中实线箭头表示的就是邻接表,而虚线箭头就是此图的逆邻接表的表示。

数据结构之图_第25张图片

转载链接:http://blog.csdn.net/cdu09/article/details/8980978

你可能感兴趣的:(数据结构之图)