编写程序对给定的有向图(不一定连通)进行深度优先遍历_L3图论第08课 图的遍历...

L3-图论-第08课 图的遍历

图的遍历是指,从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。遍历过程中得到的顶点序列称为图遍历序列。

图的遍历过程中,根据搜索方法的不同,又可以划分为两种搜索策略:

  • 深度优先搜索(DFS,Depth First Search)

  • 广度优先搜索(BFS,Breadth First Search)

深度优先搜索 DFS

DFS 全称是(Depth First Search),中文名是深度优先搜索,是一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。

该算法讲解时常常与 BFS 并列,但两者除了都能遍历图的连通块以外,用途完全不同,很少有能混用两种算法的情况。

DFS 最显著的特征在于其 「递归调用自身」 。同时与 BFS 类似,DFS 会对其访问过的点打上访问标记,在遍历图时跳过已打过标记的点,以确保 「每个点仅访问一次」 。符合以上两条规则的函数,便是广义上的 DFS。

算法思想
  1. 从顶点 vi 出发, 访问, 标记, 找邻接顶点 vi1
  2. 从vi1 出发, DFS访问和它邻接的所有顶点
  3. 转到第一步, 直到所有和vi邻接的顶点全部被访问.
  4. 继续选取图中其他未访问的顶点作为起始顶点, 转到第一步.

具体地说,DFS 大致结构如下:

    DFS(v) // v 可以是图中的一个顶点,也可以是抽象的概念,如 dp 状态等。
在 v 上打访问标记
for u in v 的相邻结点
if u 没有打过访问标记 then
DFS(u)
end
end
end

该算法通常的时间复杂度为 ,空间复杂度为 ,其中 表示点数, 表示边数。注意空间复杂度包含了栈空间,栈空间的空间复杂度是 的。在平均 遍历一条边的条件下才能达到此时间复杂度,例如用前向星或邻接表存储图;如果用邻接矩阵则不一定能达到此复杂度。

参考代码

以链式前向星为例:

void dfs(int u) {
  cout <" ";
  vis[u] = 1;
  for (int ei = head[u]; ei; ei = e[ei].next) {
    if (!vis[e[i].to]) {
      dfs(e[i].to);
    }
  }
}

DFS 序列

DFS 序列是指 DFS 调用过程中访问的节点编号的序列。

我们发现,每个子树都对应 DFS 序列中的连续一段(一段区间)。

一般图上 DFS

对于非连通图,只能访问到起点所在的连通分量。

对于连通图,DFS 序列通常不唯一。

注:树的 DFS 序列也是不唯一的。

在 DFS 过程中,通过记录每个节点从哪个点访问而来,可以建立一个树结构,称为 DFS 树。DFS 树是原图的一个生成树。

[USACO06DEC]Cow Picnic S

题目描述

The cows are having a picnic! Each of Farmer John's K (1 ≤ K ≤ 100) cows is grazing in one of N (1 ≤ N ≤ 1,000) pastures, conveniently numbered 1...N. The pastures are connected by M (1 ≤ M ≤ 10,000) one-way paths (no path connects a pasture to itself).

The cows want to gather in the same pasture for their picnic, but (because of the one-way paths) some cows may only be able to get to some pastures. Help the cows out by figuring out how many pastures are reachable by all cows, and hence are possible picnic locations.

K(1≤K≤100)只奶牛分散在N(1≤N≤1000)个牧场.现在她们要集中起来进餐.牧场之间有M(1≤M≤10000)条有向路连接,而且不存在起点和终点相同的有向路.她们进餐的地点必须是所有奶牛都可到达的地方.那么,有多少这样的牧场呢?

输入格式

Line 1: Three space-separated integers, respectively: K, N, and M

Lines 2..K+1: Line i+1 contains a single integer (1..N) which is the number of the pasture in which cow i is grazing.

Lines K+2..M+K+1: Each line contains two space-separated integers, respectively A and B (both 1..N and A != B), representing a one-way path from pasture A to pasture B.

输出格式

Line 1: The single integer that is the number of pastures that are reachable by all cows via the one-way paths.

输入输出样例
  • 输入 #1复制
2 4 4
2
3
1 2
1 4
2 3
3 4
  • 输出 #1复制

2

说明/提示

The cows can meet in pastures 3 or 4.

分析

牧场个数是 1000 个, 使用邻接矩阵存储空间上是没问题的. 求所有奶牛都能到的牧场, 那就让奶牛遍历所有的能到达的点, 并且标记好. 那么被所有奶牛标记过的牧场就是所求的.

邻接矩阵
#include 
#include 
using namespace std;

const int MAXN = 1000 + 6;

int k, n, m, ans;
int g[MAXN][MAXN], cow[MAXN];
int vis[MAXN], mk[MAXN];

int dfs(int x)
{
 mk[x]++;
 vis[x] = 1;
 for (int i = 1; i <= n; i++)
 {
  if (g[x][i] && vis[i] == 0)
  {
   dfs(i);
  }
 }
}

int main()
{
 ios::sync_with_stdio(false);
 cin >> k >> n >> m;

 for (int i = 1; i <= k; i++)
 {
  cin >> cow[i];
 }

 for (int i = 1; i <= m; i++)
 {
  int x, y;
  cin >> x >> y;
  g[x][y] = 1;
 }

 for (int i = 1; i <= k; i++)
 {
  memset(vis, 0, sizeof(vis));
  dfs(cow[i]);
 }

 for (int i = 1; i <= n; i++)
 {
  if (mk[i] == k)
   ans++;
 }

 cout < return 0;
}

广度优先搜索 BFS

BFS 全称是 [Breadth First Search] 中文名是宽度优先搜索,也叫广度优先搜索。是图上最基础、最重要的搜索算法之一。

所谓宽度优先。就是每次都尝试访问同一层的节点。如果同一层都访问完了,再访问下一层。

这样做的结果是,BFS 算法找到的路径是从起点开始的 「最短」 合法路径。换言之,这条路所包含的边数最小。

在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。

算法思想

伪代码:

bfs(s) {
q = new queue()
q.push(s);
visited[s] = true
while (!q.empty()) {
u = q.pop()
for each edge(u, v) {
if (!visited[v]) {
q.push(v)
visited[v] = true
}
}
}
}
参考代码

C++:

void bfs(int u) {
  while (!Q.empty())
    Q.pop();
  Q.push(u);
  vis[u] = 1;
  d[u] = 0;
  p[u] = -1;
  while (!Q.empty()) {
    u = Q.front();
    Q.pop();
    for (int i = head[u]; i; i = e[i].next) {
      if (!vis[e[i].to]) {
        Q.push(e[i].to);
        vis[e[i].to] = 1;
        d[e[i].to] = d[u] + 1;
        p[e[i].to] = u;
      }
    }
  }
}
void restore(int x) {
  vector<int> res;
  for (int v = x; v != -1; v = p[v]) {
    res.push_back(v);
  }
  std::reverse(res.begin(), res.end());
  for (int i = 0; i printf("%d", res[i]);
  puts("");
}

具体来说,我们用一个队列 Q 来记录要处理的节点,然后开一个 布尔数组来标记某个节点是否已经访问过了。

开始的时候,我们把起点 s 以外的节点的 vis 值设为 0,意思是没有访问过。然后把起点 s 放入队列 Q 中。

之后,我们每次从队列 Q 中取出队首的点 u,把 u 相邻的所有点 v 标记为已经访问过了并放入队列 Q。

直到某一时刻,队列 Q 为空,这时 BFS 结束。

在 BFS 的过程中,也可以记录一些额外的信息。比如上面的代码中,d 数组是用来记录某个点到起点的距离(要经过的最少边数),p 数组是记录从起点到这个点的最短路上的上一个点。

有了 d 数组,可以方便地得到起点到一个点的距离。

有了 p 数组,可以方便地还原出起点到一个点的最短路径。上面的 restore 函数就是在做这件事:restore(x) 输出的是从起点到 x 这个点所经过的点。

时间复杂度

空间复杂度 (vis 数组和队列)

BFS 序列

BFS 序列通常也不唯一。

类似的我们也可以定义 BFS 树:在 BFS 过程中,通过记录每个节点从哪个点访问而来,可以建立一个树结构,即为 BFS 树。

应用

  • 在一个无权图上求从起点到其他所有点的最短路径。

  • 在 时间内求出所有连通块。(我们只需要从每个没有被访问过的节点开始做 BFS,显然每次 BFS 会走完一个连通块)

  • 如果把一个游戏的动作看做是状态图上的一条边(一个转移),那么 BFS 可以用来找到在游戏中从一个状态到达另一个状态所需要的最小步骤。

  • 在一个边权为 0/1 的图上求最短路。(需要修改入队的过程,如果某条边权值为 0,且可以减小边的终点到图的起点的距离,那么把边的起点加到队列首而不是队列尾)

  • 在一个有向无权图中找最小环。(从每个点开始 BFS,在我们即将抵达一个之前访问过的点开始的时候,就知道遇到了一个环。图的最小环是每次 BFS 得到的最小环的平均值。)

  • 找到一定在 最短路上的边。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一条边 ,如果 ,则说明该边在最短路上)

  • 找到一定在 最短路上的点。(分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一个点 v,如果 ,则说明该点在最短路上)

  • 找到一条长度为偶数的最短路。(我们需要一个构造一个新图,把每个点拆成两个新点,原图的边 变成 和 。对新图做 BFS, 和 之间的最短路即为所求)

P5318 【深基18.例3】查找文献

题目描述

小K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干个(也有可能没有)参考文献的链接指向别的博客文章。小K 求知欲旺盛,如果他看了某篇文章,那么他一定会去看这篇文章的参考文献(如果他之前已经看过这篇参考文献的话就不用再看它了)。

假设洛谷博客里面一共有 篇文章(编号为 1 到 n)以及 条参考文献引用关系。目前小 K 已经打开了编号为 1 的一篇文章,请帮助小 K 设计一种方法,使小 K 可以不重复、不遗漏的看完所有他能看到的文章。

这边是已经整理好的参考文献关系图,其中,文献 X → Y 表示文章 X 有参考文献 Y。不保证编号为 1 的文章没有被其他文章引用。

5b194b87694e609be2aa7ba4d0bdff87.png

请对这个图分别进行 DFS 和 BFS,并输出遍历结果。如果有很多篇文章可以参阅,请先看编号较小的那篇(因此你可能需要先排序)。

输入格式

输出格式

输入输出样例
  • 输入 #1复制
8 9
1 2
1 3
1 4
2 5
2 6
3 7
4 7
4 8
7 8
  • 输出 #1复制
1 2 5 6 3 7 8 4
1 2 3 4 5 6 7 8
分析

文章数是 所以邻接矩阵不能使用, 得用邻接表或者链式前向星来存储才可以. 要求输出时从编号小的开始, 所以需要对边进行排序.

参考代码
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAXN = 1e6 + 8;
struct Edge
{
 int u, v;
} es[MAXN];
vector hd[MAXN];
bool vis[MAXN] = {0};
int n, m;
bool cmp(Edge &e1, Edge &e2)
{if (e1.v == e2.v)return e1.u return e1.v }
void dfs(int x)
{
 vis[x] = 1;
 cout <" ";for (int i = 0; i  {
  int point = es[hd[x][i]].v;if (!vis[point])
  {
   dfs(point);
  }
 }
}
void bfs(int x)
{
 queue q;
 memset(vis, 0, sizeof(vis));
 q.push(x);
 cout <" ";
 vis[x] = true;while (!q.empty())
 {
  int u = q.front();for (int i = 0; i   {
   int point = es[hd[u][i]].v;if (!vis[point])
   {
    q.push(point);
    cout <" ";
    vis[point] = true;
   }
  }
  q.pop();
 }
}
int main()
{
 ios::sync_with_stdio(false);
 cin >> n >> m;for (int i = 1; i <= m; i++)
 {
  cin >> es[i].u >> es[i].v;
 }
 sort(es + 1, es + 1 + m, cmp);for (int i = 1; i <= m; i++)
  hd[es[i].u].push_back(i);
 dfs(1);
 cout < bfs(1);return 0;
}

题单

  • P5318 【深基18.例3】查找文献
  • P3916 图的遍历
  • P1113 杂务
  • P1807 最长路
  • P1127 词链
  • P2853 [USACO06DEC]Cow Picnic S

云帆优培订阅号:

0bcfad83f5bafb36e19d125e8ebf21ae.png

云帆优培服务号:

                    2e14e7143ca955aad84c73c8fef7605a.png

云帆优培老师联系方式:

云帆老师

微信:

4b01068d3985d695a6396019ebbd1184.png

eea536ba961697c44abd741c7c92e0fe.png

云帆优培介绍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值