图的存储结构本文的重点在于图的深度优先搜索(DFS)和广度优先搜索(BFS),因此不再对图的基本概念做过多的介绍,但是要先大致了解下图的几种常见的存储结构。 邻接矩阵邻接矩阵既可以用来存储无向图,也可以用来存储有向图。该结构实际上就是用一个二维数组(邻接矩阵)来存储顶点的信息和顶点之间的关系(有向图的弧或无向图的边)。其描述形式如下:
上面两个图均为无权图,我们假设存储的时候,V0的序号为0,V1的序号为1,V2的序号为2。。。,且adj为1表示两顶点间没有没有连接,为0时表示有连接。则有向图的邻接矩阵如下图左边的矩阵所示,无向图的邻接矩阵如下图右边的矩阵所示; 根据邻接矩阵很容易判断图中任意两个顶点之间连通与否,并可以求出各个顶点的度。 1、对于无向图,观察右边的矩阵,发现顶点Vi的度即是邻接矩阵中第i行(或第i列)的元素之和。 2、对于有向图,由于需要分别计算出度和入读,观察左边的矩阵,发现顶点Vi的出度即为邻接矩阵第i行元素之和,入度即为邻接矩阵第i列元素之和,因此顶点Vi的度即为邻接矩阵中第i行元素和第i列元素之和。 很明显,邻接矩阵所占用的存储空间与图的边数或弧数无关,因此适用于边数或弧数较多的稠密图。 邻接表邻接表是图的一种链式存储结构,既适合于存储无向图,也适合于存储有向图。在邻接表中,用一个一维数组存储图中的每个顶点的信息,同时为每个顶点建立一个单链表,链表中的节点保存依附在该顶点上的边或弧的信息。其描述形式如下:
依然以上面的有向图和无向图为例,采用邻接表来存储,可以得到对应的存储形式如下: 在邻接表上容易找到任意一个顶点的第一个邻接点和下一个邻接点,但是要判定任意两个顶点之间是否有边或弧需搜索两个顶点对应的链表,不及邻接矩阵方便。 很明显,邻接表所占用的存储空间与图的边数或弧数有关,因此适用于边数或弧数较少的稀疏图。
其思想也很容易理解,这里不再细说。 在十字链表中,既容易找到以某个顶点为尾的弧,也容易找到以某个顶点为头的弧,因而容易求得顶点的出度和入度。
图的遍历图的遍历比树的遍历要复杂的多,因为图的任一顶点都有可能与其他的顶点相邻接,因此,我们需要设置一个辅助数组visited[]来标记某个节点是否已经被访问过。图的遍历通常有两种方法:深度优先遍历(BFS)和广度优先遍历(DFS),两种遍历方式的思想都不难理解。下面我们就以如下图所示的例子来说明图的这两种遍历方式的基本思想,并用邻接表作为图的存储结构,给出BFS和DFS的实现代码(下图为无向图,有向图的BFS和DFS实现代码与无向图的相同): 我们以邻接表作为上图的存储结构,并将A、B、C、D、E、F、G、H在顶点数组中的序号分别设为0、1、2、3、4、5、6、7。我们根据上图所包含的信息,精简了邻接表的存储结构,采取如下所示的结构来存储图中顶点和边的相关信息:
那么用邻接表来表示上图中各顶点间的关系,如下图所示: 深度优先搜索深度优先搜索(DFS)类似于树的先序遍历(如尚未掌握图的先序遍历,请移步这里:http://blog.csdn.net/ns_code/article/details/12977901)。其基本思想是:从图中某个顶点v出发,遍历该顶点,而后依次从v的未被访问的邻接点出发深度优先遍历图中,直到图中所有与v连通的顶点都已被遍历。如果当前图只是需要遍历的非连通图的一个极大连通子图,则另选其他子图中的一个顶点,重复上述遍历过程,直到该非连通图中的所有顶点都被遍历完。很明显,这里要用到递归的思想。结合上面的例子来分析,假设从A点开始遍历该图,根据图中顶点的存储关系,会按照下面的步骤来遍历该图: 1、在访问完顶点A后,选择A的第一个邻接点B进行访问; 2、而后看B的邻接点,由于B的第一个邻接点A已经被访问过,故选择其下一个邻接点D进行访问; 3、而后看D的邻接点,由于D的第一个邻接点B已经被访问过,故选择其下一个邻接点H进行访问; 4、而后看H的邻接点,由于H的第一个邻接点D已经被访问过,故选择其下一个邻接点E进行访问; 5、而后看E的邻接点,由于E的第一个邻接点B已经被访问过,那么看其第二个邻接点H,也被访问过了,E的邻接点已经全部被访问过了。 6、这时退回到上一层递归,回到顶点H,同样H的邻接点也都被访问完毕,再退回到D,D的邻接点也已访问完毕,再退回到B,一直退回到A; 7、由于A的下一个邻接点C还没有被访问,因此访问C; 8、而后看C的邻接点,由于C的第一个邻接点A已经被访问过,故选择其下一个邻接点F进行访问; 9、而后看F的邻接点,由于F的第一个邻接点C已经被访问过,故选择其下一个邻接点G进行访问; 10、而后看G的邻接点,由于G的临界点都已被访问完毕,因此会退到上一层递归,看F顶点; 11、同理,由F再回退到C,再由C回退到A,这是第一层的递归,A的所有邻接点也已都被访问完毕,遍历到此结束。 |