DFS深度优先遍历算法详解(Java实现)
在数据结构中,图的深度优先遍历是树的先根遍历的推广。
图的深度优先遍历的思想是:从图中某个顶点 v0 出发,访问顶点 v0 的第一个邻接点,然后以该邻接点为新的顶点,访问该顶点的邻接点。重复执行以上操作,直到当前顶点没有邻接点为止。返回上一个已经访问过并且还有未被访问的邻接点的顶点,按照以上步骤继续访问该顶点的其他未被访问的邻接点。以此类推,直到图中所有的顶点都被访问过。
图的深度优先遍历如下图所示:

图 1 无向图 G6 及其深度优先遍历过程
访问顶点的方向用实箭头表示,回溯用虚箭头表示,图中的数字表示访问或回溯的次序。
无向图 G6 的深度优先遍历过程如下:
因此,图的深度优先遍历的序列为:A、B、E、F、C、D、G、H、I。
在图的深度优先遍历过程中,图中可能存在回路,因此,在访问某个顶点之后,沿着某条路径遍历,有可能又回到该顶点。例如,在访问顶点 A 之后,接着访问顶点 B、E、F、C,顶点 C 的邻接点是顶点 A,沿着边 (C,A) 会再次访问顶点 A。为了避免再次访问已经访问过的顶点,需要设置一个数组 visited[n],作为一个标志记录结点是否被访问过。
在上面的算法中,对于查找序号为 v 的顶点的第一个邻接点的算法 FirstAdjVex(G,G.vexs[v]) 以及查找序号为 v 的相对于序号 w 的下一个邻接点的算法 NextAdjVex(G,G.vexs[v],G.vexs[w]) 的实现,采用不同的存储表示,其耗费的时间也是不一样的。当采用邻接矩阵作为图的存储结构时,若图的顶点个数为 n,则查找顶点的邻接点需要的时间为
以邻接表作为存储结构,查找 v 的第一个邻接点的算法实现如下:
以邻接表作为存储结构,查找 v 相对于 w 的下一个邻接点的算法实现如下:
图的非递归深度优先遍历的算法实现如下:
图的深度优先遍历的思想是:从图中某个顶点 v0 出发,访问顶点 v0 的第一个邻接点,然后以该邻接点为新的顶点,访问该顶点的邻接点。重复执行以上操作,直到当前顶点没有邻接点为止。返回上一个已经访问过并且还有未被访问的邻接点的顶点,按照以上步骤继续访问该顶点的其他未被访问的邻接点。以此类推,直到图中所有的顶点都被访问过。
图的深度优先遍历如下图所示:

图 1 无向图 G6 及其深度优先遍历过程
访问顶点的方向用实箭头表示,回溯用虚箭头表示,图中的数字表示访问或回溯的次序。
无向图 G6 的深度优先遍历过程如下:
- 首先访问 A,顶点 A 的邻接点有 B、C、D,然后访问 A 的第一个邻接点 B。
- 顶点 B 未访问的邻接点只有顶点 E,因此访问顶点 E。
- 顶点 E 的邻接点只有 F 且未被访问过,因此访问顶点 F。
- 顶点 F 的邻接点只有 C 且未被访问过,因此访问顶点 C。
- 顶点 C 的邻接点只有 A 但已经被访问过,因此要回溯到上一个顶点 F。
- 同理,顶点 F、E、B 都已经被访问过,且没有其他未访问的邻接点,因此回溯到顶点 A。
- 顶点 A 未被访问的邻接顶点只有顶点 D,因此访问顶点 D。
- 顶点 D 的邻接点有顶点 G 和顶点 H,访问第一个顶点 G。
- 顶点 G 的邻接点有顶点 H 和顶点 I,访问第一个顶点 H。
- 顶点 H 的邻接点只有 D 且已经被访问过,因此回溯到上一个顶点 G。
- 顶点 G 的未被访问过的邻接点有顶点 I,因此访问顶点 I。
- 顶点I已经没有未被访问的邻接点,因此回溯到顶点 G。
- 同理,顶点 G、D 都没有未被访问的邻接点,因此回溯到顶点 A。
- 顶点 A 也没有未被访问的邻接点。
因此,图的深度优先遍历的序列为:A、B、E、F、C、D、G、H、I。
在图的深度优先遍历过程中,图中可能存在回路,因此,在访问某个顶点之后,沿着某条路径遍历,有可能又回到该顶点。例如,在访问顶点 A 之后,接着访问顶点 B、E、F、C,顶点 C 的邻接点是顶点 A,沿着边 (C,A) 会再次访问顶点 A。为了避免再次访问已经访问过的顶点,需要设置一个数组 visited[n],作为一个标志记录结点是否被访问过。
深度优先遍历算法的实现
图的深度优先遍历(邻接表实现)的算法描述如下:public void DFSTraverse(int visited[]) // 从第 1 个顶点起,深度优先遍历图 { for (int v = 0; v < vexnum; v++) visited[v] = 0; // 访问标志数组初始化为未被访问过 for (int v = 0; v < vexnum; v++) { if (visited[v] == 0) // 对未访问的顶点 v 进行深度优先遍历 DFS(v, visited); } System.out.println(); } public void DFS(int v, int visited[]) // 从顶点 v 出发递归深度优先遍历图 { visited[v] = 1; // 访问标志设置为已访问 System.out.print(vertex[v].data + " "); // 访问第 v 个顶点 int w = FirstAdjVertex(vertex[v].data); while (w >= 0) { if (visited[w] == 0) DFS(w, visited); // 递归调用 DFS 对 v 的尚未访问的邻接顶点 w = NextAdjVertex(vertex[v].data, vertex[w].data); } }如果该图是一个无向连通图或者该图是一个强连通图,则只需要调用一次 DFS(G,v) 就可以遍历整个图,否则需要多次调用 DFS(G,v)。
在上面的算法中,对于查找序号为 v 的顶点的第一个邻接点的算法 FirstAdjVex(G,G.vexs[v]) 以及查找序号为 v 的相对于序号 w 的下一个邻接点的算法 NextAdjVex(G,G.vexs[v],G.vexs[w]) 的实现,采用不同的存储表示,其耗费的时间也是不一样的。当采用邻接矩阵作为图的存储结构时,若图的顶点个数为 n,则查找顶点的邻接点需要的时间为
O(n^2)
。若无向图中的边数或有向图的弧的数目为 e,当采用邻接表作为图的存储结构时,则查找顶点的邻接点需要的时间为 O(e)
。以邻接表作为存储结构,查找 v 的第一个邻接点的算法实现如下:
public int FirstAdjVertex(String v) // 返回顶点 v 的第一个邻接顶点的序号 { int v1 = LocateVertex(v); // v1 为顶点 v 在图 G 中的序号 ArcNode p = vertex[v1].firstarc; if (p != null) // 若顶点 v 的第一个邻接点存在,则返回邻接点的序号,否则返回-1 return p.adjvex; else return -1; }
以邻接表作为存储结构,查找 v 相对于 w 的下一个邻接点的算法实现如下:
public int NextAdjVertex(String v, String w) // 返回 v 相对于 w 的下一个邻接顶点的序号 { int v1 = LocateVertex(v); // v1 为顶点 v 在图 G 中的序号 int w1 = LocateVertex(w); // w1 为顶点 w 在图 G 中的序号 ArcNode next = vertex[v1].firstarc; while (next != null) { if (next.adjvex != w1) next = next.nextarc; else break; } ArcNode p = next; // p 指向顶点 v 的邻接顶点 w 的结点 if (p == null || p.nextarc == null) // 若 w 不存在或 w 是最后一个邻接点,则返回-1 return -1; else return p.nextarc.adjvex; // 返回 v 相对于 w 的下一个邻接点的序号 }
图的非递归深度优先遍历的算法实现如下:
public void DFSTraverse2(int v, int visited[]) // 图的非递归深度优先遍历 { ArcNode stack[] = new ArcNode[vexnum]; for (int i = 0; i < vexnum; i++) // 为所有顶点添加未访问标志 visited[i] = 0; System.out.print(vertex[v].data + " "); // 访问顶点 v 并将访问标志置为 1,表示已被访问过 visited[v] = 1; int top = -1; // 初始化栈 ArcNode p = vertex[v].firstarc; // p 指向顶点 v 的第一个邻接点 while (top > -1 || p != null) { while (p != null) { if (visited[p.adjvex] == 1) // 若 p 指向的顶点已经访问过,则 p 指向下一个邻接点 p = p.nextarc; else { System.out.print(vertex[p.adjvex].data + " "); // 访问 p 指向的顶点 visited[p.adjvex] = 1; top++; stack[top] = p; // 保存 p 指向的顶点 p = vertex[p.adjvex].firstarc; // p 指向当前顶点的第一个邻接点 } } if (top > -1) { p = stack[top--]; // 若当前顶点都已经被访问过,则退栈 p = p.nextarc; // p 指向下一个邻接点 } } }