Depth First Search ---- 深度优先遍历
1.相关基础:
1.1 图的存储结构
基于二维数组的邻接矩阵表示 + 基于链表的的邻接表。
说明:
如下为无向图的邻接矩阵的表示,V[ i ][ j ] = 1 表示连接;V[ i ][ j ] = 0表示不连接;
1.2 图的遍历
从图的某个顶点出发,访问图中的所有顶点,且使每个顶点仅被访问一次。
---------------------------------------------------------------------------------------------------------------------------
2.深度优先算法
2.1 思想
G(V , E)是一个有向图或者无向图1
做标记:开始时,图 G 的所有顶点都未曾被访问过;设置 visit [ n ] = 0 ; 当顶点被访问过后,设置参数变成1
选择出发点:从 G 中选取任意一点 u 作为出发点;
流程:
1)从初始点(出发点)开始,把其标注为已经访问后,依次搜索 u 的每一个邻接顶点 v(若 v2 和 v3 都是其邻接节点,则可规定先从数字小的顶点开始,即 v2 )
2)如果该顶点 v 未被访问过,则从 v 开始重复进行第一步的动作 , 以此进行深度优先遍历
3)如果 u 的所有邻接顶点 v 均被访问过,如果图中所有节点全部被访问过一次,则遍历结束;如果图中仍有未被访问过的节点,则任意选取一个重新开始步骤1。
2.2 流程&例子
如图为一个无向连接图:深度遍历依次访问的节点为:1 -- 2 -- 4 -- 8 -- 5 -- 3 -- 6 -- 7
(如下的流程描述使用的是数组。但是也可以使用其他的数据结构比如栈)
流程:
1)首先选取 V1 为初始节点 ;输出 V1,标记 V1 的flag=true;
2)获得 V1 的邻接边 [ V2 V3 ], 取出 V2,标记 V2 的flag=true; (此处安装数字小的先遍历的原则,即2,3,先遍历2)
3)获得 V2 的邻接边[ V1 V4 V5 ], 过滤掉已经 flag 的,取出 V4,标记 V4 的flag=true;
4)获得 V4 的邻接边[ V2 V8 ], 过滤掉已经 flag的,取出 V8,标记 V8 的flag=true;
5)获得 V8 的邻接边[ V4 V5 ], 过滤掉已经 flag的,取出 V5,标记 V5 的flag=true;
6) 此时发现 V5 的所有邻接边都已经被flag了,所以需要回溯。(左边黑色虚线,回溯到 V1,回溯就是下层递归结束往回返)
特殊说明:在这里为了区分已经访问过的节点和没有访问过的节点,灰色的节点表示已经访问过了。原来图中 V2 只与上面的 V1 连接, 此处为什么在 V2 处又添加了一个 V1 就是为了说明,找的是每个节点的邻接节点,然后对找到的节点进行筛选。
7) 回溯到 V1,在前面取出的是 V2,现在取出 V3,标记 V3 的flag=true;
8) 获得 V3 的邻接边[ V1 V6 V7 ],过滤掉已经 flag 的, 取出 V6,标记 V6 的flag=true;
9)获得V6的邻接边[V3 V7],过滤掉已经flag的,取出 V7,标记V7的flag=true;
10)此时发现 V7 的所有邻接边都已经被 flag了,所以需要回溯。(右边黑色虚线,回溯到 V1,回溯就是下层递归结束往回返)
2.3 编码
使用数组的回溯法进行实现:
bool visited[MaxVnum];
void DFS(Graph G,int v)
{
visited[v]= true; //从V开始访问,flag它
printf("%d",v); //打印出V
for(int j=0;j<G.vexnum;j++)
if(G.arcs[v][j]==1 && visited[j]== false) //这里可以获得V未访问过的邻接点
DFS(G,j); //递归调用,如果所有节点都被访问过,就回溯,而不再调用这里的DFS
}
void DFSTraverse(Graph G) {
for (int v = 0; v < G.vexnum; v++)
visited[v] = false; //刚开始都没有被访问过
for (int v = 0; v < G.vexnum; ++v)
if (visited[v] == false) //从没有访问过的第一个元素来遍历图
DFS(G, v);
}
数据结构的定义:
#define MaxVnum 50
typedef double AdjMatrix[MaxVnum][MaxVnum]; //表示一个矩阵,用来存储顶点和边连接关系
typedef struct {
int vexnum,arcnum; //顶点的个数,边的个数
AdjMatrix arcs; //图的邻接矩阵
}Graph;
图的初始化:
void CreateGraph(Graph &G)
{
G.vexnum=8;
G.arcnum=9;
G.arcs[0][1]=1;
G.arcs[0][2]=1;
G.arcs[1][3]=1;
G.arcs[1][4]=1;
G.arcs[2][5]=1;
G.arcs[2][6]=1;
G.arcs[3][1]=1;
G.arcs[3][7]=1;
G.arcs[3][6]=1;
G.arcs[4][1]=1;
G.arcs[4][7]=1;
G.arcs[5][2]=1;
G.arcs[5][6]=1;
G.arcs[5][5]=1;
G.arcs[6][2]=1;
G.arcs[6][5]=1;
G.arcs[7][3]=1;
G.arcs[7][4]=1;
}
2.4 使用栈来实现
当使用栈来存储的时候,我们仍需要一个数组 visit [ n ] 来判断节点是否曾经进入过栈中(也就是上面说的是否被遍历过的意思)。 算法的流程与使用数组的时候几乎相同,但是要注意使用数组的时候,如果有 V2 和 V3 同时出现,我们人为的规定先遍历数组小的节点也就是 V2。但是当我们使用栈的时候,如果让要保持先遍历数字小的节点,我们就必须要先让数字大的节点先入栈(因为栈的后进先出的原则)。
流程:
1)选取 V1,让 V1 进栈 。(此时栈内只有 V1 ,访问的节点依次为 V1)
2)弹出栈顶节点 V1 出栈,然后让它的邻接节点进栈;
3)V1 的邻接节点【V2 V3】, 我们让 V3 先进栈, V2 后压入 。(此时栈内从栈底到栈顶依次为 V3 V2 )
4)弹出栈顶的元素 V2 ,其邻接节点为【V1 V4 V5】,V1访问过了,让其为被访问的节点 V4, V5 进栈(此时栈内顶点依次为 V3 V5 V4;访问的节点依次为 V1--V2)
5)弹出栈顶的元素 V4 ,其邻接节点为【V2 V8】,V2访问过了,让其为被访问的节点 V8 进栈(此时栈内顶点依次为 V3 V5 V8;访问的节点依次为 V1-- V2 -- V4)
6)弹出栈顶的元素 V8 ,其邻接节点为【V4 V5】,V4,V5 都进过栈内,因此无节点入栈(此时栈内顶点依次为 V3 V5 V8;访问的节点依次为 V1-- V2 -- V4 -- V8)
7)弹出栈顶的元素 V5 ,其邻接节点为【V2 】,V2访问过了,因此无节点入栈(此时栈内顶点为 V3 ;访问的节点依次依次为 V1-- V2 -- V4 -- V8 -- V5)
8)弹出栈顶的元素 V3,其邻接节点为【V1 V6 V7】,V1访问过了,让其为被访问的节点 V6, V,7 进栈(此时栈内顶点依次为 V7 V6 ;访问的节点依次为 V1-- V2 -- V4 -- V8 -- V5 -- V3)
9)弹出栈顶的元素 V6,其邻接节点为【V3 V7】,V3,V7 都进过栈内,因此无节点入栈(此时栈内顶点依次为 V7 ;访问的节点依次为 V1-- V2 -- V4 -- V8 -- V5 -- V3 -- V6)
10)弹出栈顶的元素 V7,其邻接节点为【V3 V6】,V3,V6都进过栈内,因此无节点入栈(此时栈内顶点依次为无 ;访问的节点依次为 V1-- V2 -- V4 -- V8 -- V5 -- V3 -- V6 -- V7)
因此最终遍历结果为: V1-- V2 -- V4 -- V8 -- V5 -- V3 -- V6 -- V7
---------------------------------------------------------------------------------------------------------------------------
参考:https://blog.csdn.net/wang725/article/details/82120428