图的存储 搜索与图论
数据结构
空间
DFS
stack
O(n)
BFS
queue
最短路
DFS
采用stack
回溯——恢复状态
剪枝——提前回溯
遇到诸如放置、字典序等可使用深搜输出全部组合。
BFS
采用queue
边权都为1时,为最短路径
DP是特殊的最短路问题
acwing842 ——DFS
从0开始进行深搜(不从1开始是因为1也要进行排序)。
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 #include <iostream> using namespace std ; const int N = 100 ;int n; int path[N];int st[N];void dfs (int u) { if (u== n) { for (int i = 0 ;i < n; i ++) printf ("%d" , path[i]); puts ("" ); return ; } for (int i = 1 ; i <= n; i ++) { if (!st[i]) path[u] = i; st[i] = true ; dfs(u + 1 ); st[i] = false ; } } int main () { cin >> n; dfs(0 ); return 0 ; }
acwing843 ——DFS
通过枚举每行(若n皇后问题能解决,则每行必有一个皇后),再通过行表示列和对角线(计算不能放置的位置,如该行某点放置则该点列不能放置),判断条件符合则递归调用dfs。
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 #include <iostream> using namespace std;const int N = 200 ; int n;char g[N][N];bool col[N], dg[N], udg[N];void dfs (int u) { if (u == n) { for (int i = 0 ;i < n; i ++) puts (g[i]); puts ("" ); return ; } for (int i = 0 ; i < n; i ++) { if (!col[i] && !dg[u + i] && !udg[n - u + i]) { g[u][i] = 'Q' ; col[i] = dg[u + i] = udg[n - u + i] = true ; dfs (u + 1 ); col[i] = dg[u + i] = udg[n - u + i] = false ; g[u][i] = '.' ; } } } int main () { cin >> n; for (int i = 0 ; i < n; i ++) for (int j = 0 ; j < n; j ++) g[i][j] = '.' ; dfs (0 ); return 0 ; }
acwing844 ——BFS
BFS算法相当于在有m个后继点的点处分裂为m个小鼠,同时将访问过的点在数组上标定距离(该距离是最小值,因为其他点在相同步数下并不能访问该点),最后返回要求的坐标即可。
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 51 52 53 54 55 56 57 58 59 #include <iostream> #include <algorithm> #include <cstring> using namespace std ; const int N = 110 ;typedef pair <int , int > PII;int n, m; int g[N][N]; int d[N][N]; PII q[N * N]; int bfs () { int hh = 0 , tt = 0 ; q[0 ] = {0 , 0 }; memset (d, -1 , sizeof d); d[0 ][0 ] = 0 ; int dx[4 ] = {-1 , 0 , 1 , 0 }, dy[4 ] = {0 , 1 , 0 , -1 }; while (hh <= tt) { auto t = q[hh ++ ]; 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 ; q[ ++ tt] = {x,y} ; } } } return d[n - 1 ][m - 1 ]; } int main () { cin >> n >> m; for (int i = 0 ; i < n; i ++ ) for (int j = 0 ;j < m; j ++ ) cin >> g[i][j]; cout << bfs() << endl ; return 0 ; }
acwing845 ——BFS
这题我们从输入的状态出发,直到到达最终的状态,本质是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 45 46 47 48 49 50 51 52 #include <iostream> #include <queue> #include <string> #include <unordered_map> using namespace std;string finnal = "12345678x" ; unordered_map<string, int > un_map; queue<string> status; int bfs (string st) { un_map[st] = 0 ; status.push (st); int xway[4 ] = {-1 , 0 , 1 , 0 }; int yway[4 ] = {0 , -1 , 0 , 1 }; while (status.size ()) { string tmp = status.front (); status.pop (); int pos = tmp.find ('x' ); int x = pos / 3 ; int y = pos % 3 ; int distance = un_map[tmp]; if (tmp == finnal) return distance; for (int i = 0 ; i < 4 ; i++) { int a = x + xway[i], b = y + yway[i]; if (a >= 0 && a < 3 && b >= 0 && b < 3 ) { swap (tmp[pos], tmp[3 * a + b]); if (!un_map.count (tmp)) { un_map[tmp] = distance + 1 ; status.push (tmp); } swap (tmp[pos], tmp[3 * a + b]); } } } return -1 ; } int main () { string st; for (int i = 0 ; i < 9 ; i++) { char tmp; cin >> tmp; st += tmp; } cout << bfs (st) << endl; return 0 ; }
acwing846
有向图
DFS 通过idx标识每一条边,
h[a]获取该节点,然后通过ne[idx]访问对应关系,通过e[idx]访问下一节点
树的重心的性质:
还需理解
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 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 100010 , M = N * 2 ;int n;int ans = N;int h[N], e[M], ne[M], idx;bool st[N]; ·void add (int a,int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } int dfs (int u) { st[u] = true ; int sum = 1 , res = 0 ; for (int i = h[u]; i != -1 ; 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 () { memset (h, -1 , sizeof h); int a,b; cin >> n; for (int i = 0 ;i < n ; i ++) { cin >> a >>b; add (a, b), add (b, a); } dfs (1 ); cout << ans << endl; return 0 ; }
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 100010 , M = N * 2 ;int n, m;int h[N], e[M], ne[M], idx;int d[N], q[N];void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } int bfs (int u) { int hh = 0 , tt = 0 ; q[0 ] = 1 ; memset (d, -1 , sizeof d); d[1 ] = 0 ; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (d[j] == -1 ) { d[j] = d[t] +1 ; q[ ++ tt] = j; } } } return d[n]; } int main () { memset (h, -1 , sizeof h); cin >> n >> m; for (int i = 0 ;i < m ; i ++) { int a,b; cin >> a >>b; add (a, b); } cout << bfs (1 ) << endl; return 0 ; }
拓扑序列
有向无环图
一个有向无环图,一定至少有一个入度为0的点
使用数组模拟队列可以输出路径
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 100010 ;int n,m;int h[N], e[N], ne[N], idx ;int q[N], d[N];void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } int topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; d[j] --; if (d[j] == 0 ) q[++tt] = j; } } if (tt == n - 1 ){ for (int i = 0 ; i < n; i++) cout << q[i] << " " ; } else cout << -1 ; } int main () { cin >> n >> m; memset (h, -1 , sizeof h); for (int i = 0 ; i < m; i ++ ) { int a, b; cin >> a >>b; d[b]++; add (a,b); } topsort (); return 0 ; }
最短路 单源最短路 所有边权都为正数
存在负权边
Bellman-Ford算法O(nm)
SPFA算法,一般O(m), 最坏O(nm)
多源汇点最短路
邻接矩阵
邻接表《链式前向星》
AcWing 849. Dijkstra求最短路 I
朴素Dijkstra算法
将除起点外的距离都置为无穷
选取当前点到另一未访问
且距离最短
的边
更新所有点到起点的距离
重复2——3,直到到达终点
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 #include <algorithm> #include <cstring> #include <iostream> using namespace std;const int N = 510 ;int g[N][N]; int dist[N]; bool st[N]; int n, m;int Dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n; i++) { int t = -1 ; for (int j = 1 ; j <= n; j++) { if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; } st[t] = true ; for (int j = 1 ; j <= n; j++) { dist[j] = min (dist[j], dist[t] + g[t][j]); } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } int main () { cin >> n >> m; memset (g, 0x3f , sizeof g); while (m--) { int a, b, c; cin >> a >> b >> c; g[a][b] = min (g[a][b], c); } cout << Dijkstra () << endl; return 0 ; }
AcWing 850. Dijkstra求最短路 II
AcWing 850. 朴素Dijkstra与堆优化Dijkstra总结 - AcWing
使用优先队列模拟堆来实现距离的排序,同时再出队时遍历从该点出发的边实现更新最短距离值。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <cstring> #include <iostream> #include <queue> using namespace std;typedef pair<int , int > PII;const int N = 150010 ;int h[N], e[N], ne[N], idx;int w[N]; int dist[N];bool st[N];int n, m;void add (int a, int b, int c) { e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx++; } int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push ({0 , 1 }); while (heap.size ()) { auto t = heap.top (); heap.pop (); int distance = t.first, node = t.second; if (st[node]) continue ; st[node] = true ; for (int i = h[node]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[node] + w[i]) { dist[j] = dist[node] + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; else return dist[n]; } int main () { memset (h, -1 , sizeof (h)); scanf ("%d%d" , &n, &m); while (m--) { int x, y, c; scanf ("%d%d%d" , &x, &y, &c); add (x, y, c); } cout << dijkstra () << endl; return 0 ; }
AcWing 853. 有边数限制的最短路
AcWing 853. 有边数限制的最短路 - AcWing 解释了为什么Dijkstra不能使用在含负权的图中
Bellman - ford 算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在 n-1 次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
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 #include <iostream> #include <cstring> using namespace std;const int N = 510 , M = 10010 ;struct Edge { int a; int b; int w; } e[M]; int dist[N];int back[N];int n, m, k;void bellman_ford () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < k; i++) { memcpy (back, dist, sizeof dist); for (int i = 0 ; i < m; i++) { int a = e[i].a, b = e[i].b, w = e[i].w; dist[b] = min (dist[b], back[a] + w); } } } int main () { scanf ("%d%d%d" , &n, &m, &k); for (int i = 0 ; i < m; i++) { int a, b, w; scanf ("%d%d%d" , &a, &b, &w); e[i] = {a, b, w}; } bellman_ford (); if (dist[n]>0x3f3f3f3f /2 ) puts ("impossible" ); else printf ("%d" ,dist[n]); return 0 ; }
AcWing 851. spfa求最短路
AcWing 851. SPFA算法 - AcWing 包含了算法的比较
SPFA算法和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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <cstring> #include <iostream> #include <algorithm> #include <queue> using namespace std;const int N = 100010 ;int n, m;int h[N], w[N], e[N], ne[N], idx;int dist[N];bool st[N]; void add (int a, int b, int c) { e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx++; } int spfa () { memset (dist, 0x3f , sizeof dist); queue<int > q; q.push (1 ); dist[1 ] = 0 ; while (q.size ()) { int t = q.front (); q.pop (); st[t] = false ; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { st[j] = true ; q.push (j); } } } } return dist[n]; } int main () { memset (h, -1 , sizeof h); memset (dist, 0x3f , sizeof dist); int n, m; cin >> n >> m; for (int i = 0 ; i < m; i++){ int a, b, w; cin >> a >> b >> w; add (a, b, w); } spfa (); if (dist[n] == 0x3f3f3f3f ) cout << "impossible" ; else cout << dist[n]; return 0 ; }
AcWing 852. spfa判断负环
AcWing 852. spfa判断负环 - AcWing
统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则也说明存在环
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include <algorithm> #include <cstring> #include <iostream> #include <queue> using namespace std;const int N = 2e3 + 10 , M = 1e4 + 10 ;int n, m;int h[N], e[M], ne[M], w[M], idx;bool st[N];int dist[N];int cnt[N]; void add (int a, int b, int c) { e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx++; } bool spfa () { queue<int > q; for (int i = 1 ; i <= n; i++) { q.push (i); st[i] = true ; } while (q.size ()) { int t = q.front (); q.pop (); st[t] = false ; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1 ; if (cnt[j] >= n) return true ; if (!st[j]) { q.push (j); st[j] = true ; } } } } return false ; } int main () { memset (h, -1 , sizeof h); cin >> n >> m; for (int i = 0 ; i < m; i++) { int a, b, w; cin >> a >> b >> w; add (a, b, w); } if (spfa ()) cout << "Yes" ; else cout << "No" ; return 0 ; }
最小生成树
普利姆算法(Prim)
朴素版Prim O(n^2) 稠密图
堆优化版Prim O(mlogn)
克鲁斯卡尔算法(Kruskal) O(mlogm) 稀疏图
二分图
染色法 O(n+m)
匈牙利算法 O(mn),实际运行时间很小
AcWing858. Prim算法求最小生成树
Prem算法:我们从其中一个点出发,遍历该点所有边,找到最小权值的边,然后加入集合,更新该边另一顶点引出的边(使从该边引出的节点可达)。
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 51 52 53 54 55 56 57 58 59 60 #include <cstring> #include <iostream> #include <algorithm> using namespace std;const int N = 510 , INF = 0x3f3f3f3f ;int n, m;int g[N][N];int dist[N];bool st[N]; int prim () { memset (dist, 0x3f , sizeof dist); int res = 0 ; for (int i = 0 ; i < n; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; if (i && dist[t] == INF) return INF; if (i) res += dist[t]; st[t] = true ; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], g[t][j]); } return res; } int main () { scanf ("%d%d" , &n, &m); memset (g, 0x3f , sizeof g); while (m -- ) { int a, b, c; scanf ("%d%d%d" , &a, &b, &c); g[a][b] = g[b][a] = min (g[a][b], c); } int t = prim (); if (t == INF) puts ("impossible" ); else printf ("%d\n" , t); return 0 ; }
AcWing 859. Kruskal算法求最小生成树
kruskal算法:
通过并查集对边进行合并。先按照从小到大的权重进行排序,然后对每两个未连接的节点进行连边。我们知道,对于一个无向图,每两点有且仅有一条边相连,若其边数小于n-1,则其不联通,不能构成最小生成树。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 , M = 200010 , INF = 0x3f3f3f3f ;int n, m;int p[N];struct Edge { int a, b, w; bool operator <(const Edge &E) const { return w < E.w; } } edges[M]; int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } int kruskal () { sort (edges, edges + m); for (int i = 1 ; i <= n; i ++ ) p[i] = i; int res = 0 , cnt = 0 ; for (int i = 0 ; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find (a), b = find (b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1 ) return INF; else return res; } int main () { scanf ("%d%d" , &n, &m); for (int i = 0 ; i < m; i ++ ) { int a, b, w; scanf ("%d%d%d" , &a, &b, &w); edges[i] = {a, b, w}; } int t = kruskal (); if (t == INF) puts ("impossible" ); else printf ("%d\n" , t); return 0 ; }
AcWing 860. 染色法判定二分图
二分图 :二分图是一种特殊的图,其顶点集可以分成两个互不相交的子集,使得每一条边的两个端点分别属于不同的子集。
二部 :如果一个图是二分图,那么它没有奇数长度的环。
着色 :一个图是二分图当且仅当它可以被 2-着色,即可以使用两种颜色对图的顶点进行着色,使得每条边的两个端点的颜色不同。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 * 2 ;int e[N], ne[N], idx;int h[N];int color[N];int n, m;void add (int a,int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } bool dfs (int u, int c) { color[u] = c; for (int i = h[u]; i!= -1 ; i = ne[i]) { int b = e[i]; if (!color[b]) { if (!dfs (b, 3 - c)) return false ; } else if (color[b] && color[b] != 3 - c) { return false ; } } return true ; } int main () { memset (h, -1 , sizeof h); scanf ("%d%d" , &n, &m); for (int i = 0 ;i < m; i ++) { int a, b; scanf ("%d%d" , &a, &b); add (a, b); add (b, a); } for (int i = 1 ; i <= n; i++) { if (!color[i]) { if (!dfs (i, 1 )) { cout << "No" << endl; return 0 ; } } } cout << "Yes" << endl; return 0 ; }
AcWing 861. 二分图的最大匹配
匈牙利算法:
匈牙利算法是一种用于解决指派问题(Assignment Problem)的数学算法。指派问题是指在有限的候选者中选择一组匹配,使得总的成本最小(或收益最大)。
find(int u)
: 尝试为左边节点 u
找到一个匹配,使用递归方式实现深度优先搜索(DFS)。
如果右边节点 j
未被访问过,则标记为已访问。
如果右边节点 j
未匹配或者其匹配的左边节点可以重新匹配,则更新匹配关系并返回 true
。
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 51 52 #include <iostream> #include <cstring> using namespace std;const int N = 505 , M = 1e5 + 10 ;int n1,n2, m;int h[N], e[M], ne[M], idx;int match[N], st[N];void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } int find (int u) { for (int i = h[u]; i != -1 ; i = ne[i]){ int j = e[i]; if (!st[j]){ st[j] = true ; if (match[j] == 0 || find (match[j])){ match[j] = u; return true ; } } } return false ; } int main () { memset (h, -1 , sizeof h); scanf ("%d%d%d" , &n1, &n2, &m); for (int i = 0 ; i < m; i++){ int a, b; scanf ("%d%d" , &a, &b); add (a, b); } int res = 0 ; for (int i = 1 ; i <= n1; i++){ memset (st, 0 , sizeof st); if (find (i)) res++; } cout << res; return 0 ; }