图结构
线性结构中数据元素之间是一对一的关系;树结构中数据元素之间是一对多的关系;图结构中数据元素之间是多对多的关系。
图有五种存储结构:邻接矩阵、邻接表、十字链表、邻接多重表和边集数组。其中最常用的两种存储结构是邻接矩阵和邻接表。
1、图的定义
图G由两个集合V、E组成,记为G = (V,E),其中V代表的是图中的顶点的集合,E代表顶点之间的关系。E可以是空集,表示该图只有顶点而没有边。
两个顶点之间的连接方式有两种,一种是边,一种是弧。其中边是无向的,所以它构成的图被称为无向图。弧是有向的,所有由弧连接而成的图被称为有向图。
(V1, V2)代表V1与V2之间有一条边,边为无向。
<V1, V2>代表V1与V2之间有一条弧,弧为有向。
图中有几个重要的概念,需要知晓:
简单图:不存在顶点到其自身的边,且同一条边不重复。
邻接:如果(Vi,Vj)是图中的一条边,则称Vi与Vj互为邻接点。如果<Vi,Vj>是图中的一条弧,则称Vj是Vi的邻接点。
完全无向图:每一个顶点间有且仅有一条边连接。
完全有向图:每一个顶点间都有相对的两条弧连接。
度:与顶点相关联的边或弧的条数。
入度:有向图中,到达顶点的边数。
出度:有向图中,顶点出发的边数。
网:带权的图(边或者弧上带有权值)。
生成树:连通图中一个极小连通子图,即含有全部顶点,但足以构成树的n-1条边。
2、图的存储结构
图的存储结构有五种:邻接矩阵、邻接表、十字链表、邻接多重表和边集数组。
其中最常使用的两种存储方式是邻接矩阵和邻接表。
邻接矩阵的原理就是通过二维数组表示顶点之间的相邻关系。也就是说,如果两个顶点之间有边或弧的连接关系,那么他们的数组对应的位置就置1;如果没有连接关系,则置零。根据上面的规则可以知道,从左上角到右下角的斜线一定全是0。
可以知道的是:无向图的邻接矩阵一定是一个对称矩阵,而有向图的邻接矩阵可以是不对称的。
(在有向图中,以行为主顶点,列为潜在的邻接顶点。也就是说,必须要在满足(行,列)的关系下,才能将该点置为1)
思考:如果是网的邻接矩阵呢?
提示:有边或者弧的情况,值为权值。本身点值为0。其他情况(没有边或者弧),值为无穷。
如果使用邻接矩阵表示法来表示图,除了存储用于表示顶点间相邻关系的邻接矩阵外,通常还需要用一个顺序表来存储顶点信息。
邻接表:邻接表表示法类似于树的孩子表示法,是一种顺序结构和链式结构相结合的存储方法。
对于图的每一个顶点Vi,将所有邻接于Vi的顶点连接成一个单链表,称为顶点Vi的边表(对于有向图,称为出边表);对于所有顶点,使用顺序结构进行存储,称为顶点表,用来存储顶点Vi的信息和对应边表的头指针。
邻接表的存储结构中有两种结点结构:顶点结点和弧结点。顶点结点包括数据域和指针域,其中数据域存储顶点信息,指针域用于指向第一条弧。弧结点同样包含数据域和指针域,其中数据域用于存储邻接顶点下标,指针域指向下一条弧结点。
如果采用邻接表存储无向图,那么还需要存储无向图中的边权值。
(注意:在存储无向图时,图中的每一条边相当于两条弧)
在有向图中求顶点的度,采用邻接矩阵表示比采用邻接表表示更加方便清晰:因为邻接矩阵中的第i行上非零元素的个数就是顶点Vi的出度,第i列上非零元素的个数是顶点Vi的入度。入度和出度数量之和即是定点的度。
边集数组:利用两个一维数组,其中一个数组存储图中的顶点,另一个数组存储图中的边。在存储边的数组中,每个数组元素存储一条边的起点、终点和权。
图的遍历:指从图的某一顶点出发,对图中的每一个顶点访问一次且只访问一次。
所以为了避免重复访问同一顶点,必须记住每个顶点是否已经被访问过。因此,用于图的遍历算法都必须添加一个布尔向量bool visited[n],初始值为FALSE,一旦访问到了顶点Vi,则visited[i - 1]设置为TRUE。
根据搜索路径的方向不同,图的遍历方式主要分为两种:深度优先遍历和广度优先遍历。
深度优先遍历(DFS)类似于树的前序遍历。
广度优先遍历(BFS)类似于树的层序遍历。
最小生成树:连通图G的一个子图,如果该子图包含图G的所有顶点,就称该子图为图G的生成树。那么,什么是最小生成树呢?把生成树各边的权值总合称为生成树的权,并把权值最小的生成树称为图G的最小生成树。
用于生成最小生成树的算法有两种,一种是普里姆算法(Prim),另一种是克鲁斯卡尔算法(Kruskal)。