目录
图的遍历
图的遍历定义:从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。
遍历的实质:找每个顶点的邻接点的过程,如下图所示:
图的特点:图中可能存在回路,且图的任一顶点都可能与其他顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。
为了避免重复访问:设置辅助数组visited[n],用来标记每个被访问过的顶点。
初始状态visited[i]为0,顶点i被访问,改visited[i]为1,防止被多次访问。
图常用的遍历有两种:深度优先遍历(Depth_First Search--DFS)
广度优先遍历(Breadth_First Search--BFS)
深度优先遍历(DFS)
遍历过程如下图所示:
深度优先搜索沿着一条路径一直搜索下去,在无法搜索时,回退到刚刚访问过的节点。深度优先遍历是按照深度优先搜索的方式对图进行遍历的。
深度优先遍历的秘籍:后被访问的节点,其邻接点先被访问。
根据深度优先遍历的秘籍,后来者先服务,这可以借助于栈实现。递归本身就是使用栈实现的,因此使用递归的方法更方便。
算法步骤:
① 初始化图中的所有节点均未被访问。
② 从图中的某个节点v 出发,访问v 并标记其已被访问。
③ 依次检查v 的所有邻接点w ,如果w 未被访问,则从w 出发进行深度优先遍历(递归调用,重复步骤2~3)。
接下来就来跟大家讲讲一道例题方便大家理解,一个无向图如下图所示:
其深度优先遍历的过程如下所述:
① 初始化所有节点均未被访问,visited[i]=false,i =1,2,…,8。
② 从节点1出发,标记其已被访问,visited[1]=true。
③ 从节点1出发访问邻接点2,然后从节点2出发访问节点4,从节点4出发访问节点5,从节点5出发访问未被访问的邻接点。
④ 回退到刚刚访问过的节点4,节点4也没有未被访问的邻接点,回退到最近访问过的节点2,从节点2出发访问下一个未被访问的邻接点6。
⑤ 从节点6出发访问未被访问的邻接点,回退到刚刚访问过的节点2,节点2没有未被访问的邻接点,回退到最近访问过的节点1。
⑥ 从节点1出发访问下一个未被访问的邻接点3,从节点3出发访问节点7,从节点7出发访问节点8,从节点8出发访问未被访问的邻接点。
⑦ 回退到刚刚访问过的节点7,节点7也没有未被访问的邻接点,回退到最近访问过的节点3,节点3也没有未被访问的邻接点,回退到最近访问过的节点1,节点1也没有未被访问的邻接点,遍历结束。访问路径如下图所示:
∴ 深度优先遍历序列为1 2 4 5 6 3 7 8。 深度优先遍历经过的节点及边被称为深度优先生成树,如下图所示:
注意:如果深度优先遍历非连通图,则每一个连通分量都会产生一棵深度优先生成树。
代码实现:
void DFS_AM(AMGragh G , int v){ //基于邻接矩阵的深度优先遍历
cout << G.Vex[v] << "\t";
visited[v] = true;
for(int w = 0 ; w < G.vexnum ; w++){ //依次检查v 的所有邻接点
if(G.Edge[v][w] && visited[w]){ //v、w 邻接并且w 未被访问
DFS_AM(G , w); //从节点w 出发,递归深度优先遍历
}
}
}
void DFS_AL(ALGragh G , int v){ //基于邻接表的深度优先遍历
AdjNode *p;
cout << G.Vex[v].data << "\t";
visited[v] = true;
p = G.Vex[v].first;
while(p){ //依次检查v 的所有邻接点
int w = p->v; //w 为 v 的邻接点
if(!visited[w]){ //w未被访问
DFS_AL(G , w); //从w 出发,递归深度优先遍历
}
p = p->next;
}
}
void DFS_AL(ALGragh G){ //非连通图,基于邻接表的深度优先遍历
for(int i = 0 ; i < G.vexnum ; i++){ //对非连通图需要查漏点,检查未被访问的节点
if(!visited[i]){ //i 未被访问,以i 为起点再次广度优先遍历
DFS_AL(G , i); //基于邻接表,也可以替换为基于邻接矩阵DFS_AM(G , i);
}
}
}
广度优先遍历(BFS)
广度优先遍历又称为宽度优先搜索,是最常见的图的搜索方法之一。广度优先搜索指从某一点出发,一次性访问所有未被访问的邻节点,再依次从这些已访问过的邻接点出发,一层一层地访问。广度优先遍历是按照广度优先搜索的方式对图进行遍历的。(广度优先遍历类似于树的层次遍历)
广度优先遍历的秘籍:先被访问的节点,其邻节点先被访问。 根据广度优先遍历的秘籍,先来先服务,可以借助队列实现。因为对每个节点只访问一次,所以可以设置一个辅助数组 visited[i] = false,表示第 i 个节点未被访问;visited[i] = true,表示第 i 个节点已被访问。
算法步骤:
1 初始化所有节点均为被访问,并初始化一个空队列。
2 从图中的某个节点 v 出发,访问 v 并标记其已被访问,将 v 入队。
3 如果队列非空,则继续执行,否则算法结束。
4 将队头元素 v 出队,依次访问 v 的所有未被访问的邻接点,标记已被访问并入队,转向步骤3.
接下来还是给大家讲讲一道例题使大家更好地理解广度优先遍历吧!
一个有向图如下图所示,其广度优先遍历的过程如下所述:
1.初始化所有节点都未被访问,visited[i] = false,i =[1,6]。并初始化一个空队列 Q。
2 .从节点 1 出发,标记其已被访问,visited[1] = true,将节点 1 入队。
3.将队头元素 1 出队,依次访问 1 的所有未被访问的邻接点2和3,标记其已被访问并入队。
4.对队头元素2出队,将2未被访问的邻接点4标记为已被访问,并将其入队。
5.将队头元素3出队,3的邻接点2已被访问,不用处理,将未被访问的邻接点5标记为已被访问,并将其入队。
6.将队头元素4出队,4的邻接点3已被访问,不用处理,将未被访问的邻接点6标记为已被访问,并将其入队。
7.将队头元素5出队,5 的邻节点4和6已被访问,不用处理,没有未被访问的邻接点。
8.将队头元素6出队,6没有邻接点。
9.队列为空,算法结束。广度优先遍历序列为:1 2 3 4 5 6。
DFS与BFS空间复杂度相同都是O(n)(借用了堆栈或队列);
时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与遍历路径无关。