遍历图(深度遍历和广度遍历)
一:图的存储结构
1:邻接矩阵
使用二维数组来存储图的边的信息和权重,如下图所示的4个顶点的无向图
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
如果换成有向图,则如图所示的五个顶点的有向图的邻接矩阵表示如下
2:邻接表
邻接矩阵是一种不错的图存储结构,但是对于边数相对较少的图,这种结构存在空间上的极大浪费,因此找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
(1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表
如下为无向图的邻接表表示:
从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
有向图的邻接表表示:
3:十字链表
对于邻接表来说,计算顶点的入度是不方便的,那么有没有一种存储方式能够轻松的计算顶点的入度和出度呢,答案是肯定的
在十字链表中重新定义了节点的结构:
firstin表示入边表头指针,指向该顶点的入边表中第一个结点,firstout表示出边表头指针,指向该顶点的出边表中的第一个结点
重新定义的边表结构为:
其中,tailvex是指弧起点在顶点表的下表,headvex是指弧终点在顶点表的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边。如果是网,还可以增加一个weight域来存储权值。
比如下图,顶点依然是存入一个一维数组,实线箭头指针的图示完全与邻接表相同。就以顶点v0来说,firstout指向的是出边表中的第一个结点v3。所以,v0边表结点hearvex = 3,而tailvex其实就是当前顶点v0的下标0,由于v0只有一个出边顶点,所有headlink和taillink都是空的。
重点需要解释虚线箭头的含义。它其实就是此图的逆邻接表的表示。对于v0来说,它有两个顶点v1和v2的入边。因此的firstin指向顶点v1的边表结点中headvex为0的结点,如上图圆圈1。接着由入边结点的headlink指向下一个入边顶点v2,如上图圆圈2。对于顶点v1,它有一个入边顶点v2,所以它的firstin指向顶点v2的边表结点中headvex为1的结点,如上图圆圈3。
十字链表的好处就是因为把邻接表和逆邻接表整合在一起,这样既容易找到以v为尾的弧,也容易找到以v为头的弧,因而比较容易求得顶点的出度和入度。
而且除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图应用中,十字链表是非常好的数据结构模型。
这里就介绍以上三种存储结构,除了第三种存储结构外,其他的两种存储结构比较简单
三:图的遍历
1:深度优先遍历(DFS)
它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。
基本实现思想:
(1)访问顶点v;
(2)从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
(3)重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
递归实现
(1)访问顶点v;visited[v]=1;//算法执行前visited[n]=0
(2)w=顶点v的第一个邻接点;
(3)while(w存在)
if(w未被访问)
从顶点w出发递归执行该算法;
w=顶点v的下一个邻接点;
非递归实现
(1)栈S初始化;visited[n]=0;
(2)访问顶点v;visited[v]=1;顶点v入栈S
(3)while(栈S非空)
x=栈S的顶元素(不出栈);
if(存在并找到未被访问的x的邻接点w)
访问w;visited[w]=1;
w进栈;
else
x出栈;
2:广度优先遍历(BFS)
它是一个分层搜索的过程和二叉树的层次遍历十分相似,它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。
基本实现思想:
(1)顶点v入队列。
(2)当队列非空时则继续执行,否则算法结束。
(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
(4)查找顶点v的第一个邻接顶点col。
(5)若v的邻接顶点col未被访问过的,则col入队列。
(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。
直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。
广度优先遍历图是以顶点v为起始点,由近至远,依次访问和v有路径相通而且路径长度为1,2,……的顶点。为了使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问,需设置队列存储访问的顶点。
伪代码
(1)初始化队列Q;visited[n]=0;
(2)访问顶点v;visited[v]=1;顶点v入队列Q;
(3) while(队列Q非空)
v=队列Q的对头元素出队;
w=顶点v的第一个邻接点;
while(w存在)
如果w未访问,则访问顶点w;
visited[w]=1;
顶点w入队列Q;
w=顶点v的下一个邻接点。
1、深度优先搜索遍历
思想:
沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
代码如下:
package org.lxh.graph;
public class DFSTraverse {
// 构造图的边
private int[][] edges = { { 0, 1, 0, 0, 0, 1, 0, 0, 0 },
{ 1, 0, 1, 0, 0, 0, 1, 0, 1 }, { 0, 1, 0, 1, 0, 0, 0, 0, 1 },
{ 0, 0, 1, 0, 1, 0, 1, 1, 1 }, { 0, 0, 0, 1, 0, 1, 0, 1, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 0 }, { 0, 1, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 0, 0, 1, 1, 0, 1, 0, 0 }, { 0, 1, 1, 1, 0, 0, 0, 0, 0 } };
// 构造图的顶点
private String[] vertexs = { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
// 记录被访问顶点
private boolean[] verStatus;
// 顶点个数
private int vertexsNum = vertexs.length;
public void DFSTra() {
verStatus = new boolean[vertexsNum];
for (int i = 0; i < vertexsNum; i++) {
if (verStatus[i] == false) {
DFS(i);
}
}
}
// 递归深搜
private void DFS(int i) {
System.out.print(vertexs[i] + " ");
verStatus[i] = true;
for (int j = firstAdjVex(i); j >= 0; j = nextAdjvex(i, j)) {
if (!verStatus[j]) {
DFS(j);
}
}
}
// 返回与i相连的第一个顶点
private int firstAdjVex(int i) {
for (int j = 0; j < vertexsNum; j++) {
if (edges[i][j] > 0) {
return j;
}
}
return -1;
}
// 返回与i相连的下一个顶点
private int nextAdjvex(int i, int k) {
for (int j = (k + 1); j < vertexsNum; j++) {
if (edges[i][j] == 1) {
return j;
}
}
return -1;
}
// 测试
public static void main(String[] args) {
new DFSTraverse().DFSTra();
}
}
2、广度优先搜索遍历
思想:
从根节点开始,沿着树的宽度、按照层次依次遍历树的节点。
代码如下:
package org.lxh.graph;
import java.util.LinkedList;
import java.util.Queue;
public class BFSTraverse_0100 {
// 构造图的边
private int[][] edges = { { 0, 1, 0, 0, 0, 1, 0, 0, 0 },
{ 1, 0, 1, 0, 0, 0, 1, 0, 1 }, { 0, 1, 0, 1, 0, 0, 0, 0, 1 },
{ 0, 0, 1, 0, 1, 0, 1, 1, 1 }, { 0, 0, 0, 1, 0, 1, 0, 1, 0 },
{ 1, 0, 0, 0, 1, 0, 1, 0, 0 }, { 0, 1, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 0, 0, 1, 1, 0, 1, 0, 0 }, { 0, 1, 1, 1, 0, 0, 0, 0, 0 } };
// 构造图的顶点
private String[] vertexs = { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
// 记录被访问顶点
private boolean[] verStatus;
// 顶点个数
private int vertexsNum = vertexs.length;
// 广搜
private void BFS() {
verStatus = new boolean[vertexsNum];
Queue<Integer> temp = new LinkedList<Integer>();
for (int i = 0; i < vertexsNum; i++) {
if (!verStatus[i]) {
System.out.print(vertexs[i] + " ");
verStatus[i] = true;
temp.offer(i);
while (!temp.isEmpty()) {
int j = temp.poll();
for (int k = firstAdjvex(j); k >= 0; k = nextAdjvex(j, k)) {
if (!verStatus[k]) {
System.out.print(vertexs[k] + " ");
verStatus[k] = true;
temp.offer(k);
}
}
}
}
}
}
// 返回与i相连的第一个顶点
private int firstAdjvex(int i) {
for (int j = 0; j < vertexsNum; j++) {
if (edges[i][j] > 0) {
return j;
}
}
return -1;
}
// 返回与i相连的下一个顶点
private int nextAdjvex(int i, int k) {
for (int j = (k + 1); j < vertexsNum; j++) {
if (edges[i][j] > 0) {
return j;
}
}
return -1;
}
// 测试
public static void main(String args[]) {
new BFSTraverse_0100().BFS();
}
}

浙公网安备 33010602011771号