3.2:BFS 蓝桥杯(十八)中介绍过广度优先搜索(BFS)。
BFS所求出来的路径就是最短路径!
而DFS要求最短路径需要遍历所有可行路径求最小值。(太慢了!)
推荐资料: 极客时间《数据结构与算法之美》 专栏。
关于为什么BFS走到的路径一定是最短路径的解释?
在 BFS 中,我们使用了数据结构中的一个队列(queue),我们知道队列的特性是 FIFO(First In First Out),也就是先进先出。正是这个 FIFO 特性,保证了我们第一个到达目标节点一定是最短路径。
首先,我们将起点入队。然后每次入队出队,我们将队头所能拓展的相邻点都入队,就能找到这些点到起点的最短路。第一次操作,能找到距离起点为1的所有路径;第二次,能找到距离为2的所有路径;反复入队出队,就能找到到达终点的最短路。而且在搜索过程中维护的dist数组,能够保证每个节点到起点的距离都是最短路。
y总视频的讲解很妙!https://www.acwing.com/video/21/ 1:01:03,手动模拟过程能加深理解!
DFS和BFS都是暴力搜索,时间复杂度都比较高。
BFS只能求解边权为1(等权图)的最短路问题,如果边权不是1需要专门的最短路算法。
例题:844. 走迷宫(模板题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1 ,其中 0 表示可以走的路,1 表示不可通过的墙壁。 最初,有一个人位于左上角 (1 ,1 ) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。 请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。 数据保证 (1 ,1 ) 处和 (n,m) 处的数字为 0 ,且一定至少存在一条通路。 输入格式 第一行包含两个整数 n 和 m。 接下来 n 行,每行包含 m 个整数(0 或 1 ),表示完整的二维数组迷宫。 输出格式 输出一个整数,表示从左上角移动至右下角的最少移动次数。 数据范围 1 ≤n,m≤100 输入样例: 5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0 输出样例: 8
BFS数组模拟队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <cstdio> #include <iostream> #include <cstring> using namespace std ;typedef pair <int , int > PII;const int N = 110 ;char g[N][N];int d[N][N];PII q[N*N]; int n,m;int bfs () { q[0 ] = {0 ,0 }; int head = 0 ,tail = 0 ; memset (d,-1 ,sizeof d); d[0 ][0 ] = 0 ; int dx[4 ] = {-1 , 0 , 1 , 0 }, dy[4 ] = {0 , 1 , 0 , -1 }; while (head <= tail){ auto t = q[head++]; for (int i = 0 ;i < 4 ;i ++){ int x = t.first + dx[i],y = t.second + dy[i]; if (x >=0 && x < n && y >= 0 && y < m && g[x][y] == '0' && d[x][y] == -1 ){ d[x][y] = d[t.first][t.second] + 1 ; if (x == n-1 && y == m-1 ) return d[x][y]; q[++tail] = {x,y}; } } } return d[n-1 ][m-1 ]; } int main () { scanf ("%d%d" , &n, &m); for (int i = 0 ;i < n;i ++) for (int j = 0 ;j < m;j ++) scanf (" %c" , &g[i][j]); printf ("%d\n" ,bfs()); return 0 ; }
queue实现BFS。
1 2 3 4 5 6 7 8 queue <PII> q;q.push({0 ,0 }); while (q.size()){ PII t = q.front(); q.pop(); xxx; }
如何打印最短路径?
只需要维护一个Prev
数组。
完整程序: https://www.acwing.com/problem/content/submission/code_detail/7182559/。
1 2 3 4 5 6 7 8 9 10 11 PII Prev[N][N]; Prev[x][y] = t; int x = n-1 ,y = m-1 ;while (x || y){ cout << x << ' ' << y << '\n' ; PII t = Prev[x][y]; x = t.first,y = t.second; } cout << 0 << ' ' << 0 << '\n' ;
3.3:树与图的遍历 树与图的存储方式:在蓝桥杯(二一)有过介绍。
树是一种特殊的图(无环连通图),无向图又可以看成加双边的有向图,所以存储方式都参照有向图。
1.邻接矩阵(适合稠密图),空间复杂度:O(n*n),浪费空间
2.邻接表(适合稀疏图),最常用,两种实现方式
稠密图:包含很多边;稀疏图:包含较少边。
推荐文章: https://www.cnblogs.com/linfangnan/p/12745834.html。
邻接表y总模板。
1 2 3 4 5 6 7 8 9 10 11 12 13 M = N * 2 ; int h[N], e[M], ne[M], idx;void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } idx = 0 ; memset (h, -1 , sizeof h);
DFS遍历图,每个点只走一次。
DFS与BFS遍历图的时间复杂度都是:O(n+m),n是点数,m是边数。
1 2 3 4 5 6 7 8 bool st[N];void dfs (int u) { st[u] = true ; for (int i = h[u]; ~i;i = ne[i]){ int j = e[i]; if (!st[j]) dfs(j); } }
idx
:作用类似指针,每条边都唯一对应idx
值。
h[i]
:表示每个节点i的单链表起始idx
值,e[i]
:表示有向边a—>b的终点b的编号,ne[i]
:表示i指向的下一个位置的idx
值。
例题1:846. 树的重心(模板题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 给定一颗树,树中包含 n 个结点(编号 1 ∼n)和 n−1 条无向边。 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。 输入格式 第一行包含整数 n,表示树的结点数。 接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。 输出格式 输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。 数据范围 1 ≤n≤10 ^5 输入样例 9 1 2 1 7 1 4 2 8 2 5 4 3 3 9 4 6 输出样例: 4
首先手动模拟一下样例。
如何求将每个点删除之后剩余连通块点数的最大值?
采用DFS,DFS可以求出每个点的子树的大小。
以节点4为例,在DFS的时候可以求出4的每个分支的点数,回溯时就能求出以4为根的子树的点数,然后
4以上的连通块的点数=n-Size4。只要比较一下,就能求出删除节点4之后的剩余连通块点数的最大值。
树的重心一定是有最多边的节点吗?
不一定,树的重心和边数没有关系,可以找例子模拟一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <cstring> #include <algorithm> using namespace std ;#define IOS \ ios::sync_with_stdio(false ); \ cin .tie(0 ); \ cout .tie(0 ) const int N = 1e5 + 5 ,M = 2 *N;int n;int h[N],e[M],ne[M],idx;bool st[N];int ans = 0x3f3f3f3f ;void add (int a,int b) { e[idx] = b,ne[idx] = h[a],h[a] = idx ++; } int dfs (int u) { st[u] = true ; int res = 0 ,sum = 1 ; for (int i = h[u]; ~i;i = ne[i]){ int j = e[i]; if (!st[j]){ int s = dfs(j); res = max(res,s); sum += s; } } res = max(res,n - sum); ans = min(ans,res); return sum; } int main () { IOS; memset (h, -1 , sizeof h); cin >> n; int a,b; for (int i = 0 ;i < n-1 ;i ++){ cin >> a >> b; add(a,b),add(b,a); } dfs(1 ); cout << ans << '\n' ; return 0 ; }