图算法专题 图的存储
邻接矩阵可以采用一个二维数组G[][]来进行存取数据,而邻接表可以采用链表形式或者vector数组来实现
一般来说对于点数较少的图采用邻接矩阵方式比较方便,而对于点数较多的密集图采用邻接表形式比较方便
图的遍历 深度优先搜素(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 47 48 49 50 51 52 53 #include <iostream> #include <vector> using namespace std ; vector <vector <int >> result; vector <int > path; void dfs (const vector <vector <int >>& graph, int x, int n) { if (x == n) { result.push_back(path); return ; } for (int i = 1 ; i <= n; i++) { if (graph[x][i] == 1 ) { path.push_back(i); dfs(graph, i, n); path.pop_back(); } } } int main () { int n, m, s, t; cin >> n >> m; vector <vector <int >> graph(n + 1 , vector <int >(n + 1 , 0 )); while (m--) { cin >> s >> t; graph[s][t] = 1 ; } path.push_back(1 ); dfs(graph, 1 , n); if (result.size() == 0 ) cout << -1 << endl ; for (int i=0 ;i<result.size();++i) { for (int j=0 ;j<result[i].size() - 1 ;++j) { cout <<result[i][j]<<" " ; } cout <<result[i][result[i].size() - 1 ]<<"\n" ; } 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 #include <iostream> #include <vector> #include <list> using namespace std;vector<vector<int >> result; vector<int > path; void dfs (const vector<list<int >>& graph, int x, int n) { if (x == n) { result.push_back (path); return ; } for (int i : graph[x]) { path.push_back (i); dfs (graph, i, n); path.pop_back (); } } int main () { int n, m, s, t; cin >> n >> m; vector<list<int >> graph (n + 1 ); while (m--) { cin >> s >> t; graph[s].push_back (t); } path.push_back (1 ); dfs (graph, 1 , n); if (result.size () == 0 ) cout << -1 << endl; for (const vector<int > &pa : result) { for (int i = 0 ; i < pa.size () - 1 ; i++) { cout << pa[i] << " " ; } cout << pa[pa.size () - 1 ] << endl; } }
广度优先搜素(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 #define N 10000 #define INF 10000000 int n,G[N][N];bool vis[N]={false };void BFS (int u) { queue <int >q; q.push(u); vis[u]=true ; while (!q.empty()) { int u=q.front(); q.pop(); for (int v=0 ;v<n;v++) { if (vis[u]==false &&g[u][v]!=INF) { q.push(v); vis[v]=true ; } } } } void BFSTrave () { for (int u=0 ;u<n;u++) { if (vis[u]==false ) { BFS(u); } } }
邻接表版
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 #define N 1000 #define INF 100000000 vector <int >Adj[N];bool vis[N]={false };int n;void BFS (int u) { queue <int >q; q.push(u); vis[u]=true ; while (!q.empty()) { int u=q.front(); q.pop(); for (int v=0 ;v<Adj[u].size();v++) { int z=Adj[u][v]; if (vis[z]==false ) { q.push(z); vis[z]=true ; } } } } void BFSTrave () { for (int u=0 ;u<n;u++) { if (vis[u]==false ) { BFS(u); } } }
拓扑排序 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 <iostream> #include <vector> #include <queue> #include <unordered_map> using namespace std;int main () { int m, n, s, t; cin >> n >> m; vector<int > inDegree (n, 0 ) ; unordered_map<int , vector<int >> umap; vector<int > result; while (m--) { cin >> s >> t; inDegree[t]++; umap[s].push_back (t); } queue<int > que; for (int i = 0 ; i < n; i++) { if (inDegree[i] == 0 ) que.push (i); } while (que.size ()) { int cur = que.front (); que.pop (); result.push_back (cur); vector<int > files = umap[cur]; if (files.size ()) { for (int i = 0 ; i < files.size (); i++) { inDegree[files[i]] --; if (inDegree[files[i]] == 0 ) que.push (files[i]); } } } if (result.size () == n) { for (int i = 0 ; i < n - 1 ; i++) cout << result[i] << " " ; cout << result[n - 1 ]; } else cout << -1 << endl; }
时间复杂度O(n+m),空间复杂度O(n) n为顶点数,m为边数
用途:
计算工序最短用时(经典拓扑+dp)
有向无环图(DAG)判环
分级(排序、分层)
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 int n, x, index, ans;const int maxn = 10005 ;vector<int > G[maxn]; int f[maxn], t[maxn]; int indegree[maxn]; void Topo_sort () { queue<int > q; for (int i = 1 ; i <= n; ++i) { if (!indegree[i]) { q.push (i); f[i] = t[i]; } } while (!q.empty ()) { int temp = q.front (); q.pop (); for (int i = 0 ; i < G[temp].size (); ++i) { --indegree[G[temp][i]]; f[G[temp][i]] = max (f[G[temp][i]], f[temp] + t[G[temp][i]]); if (!indegree[G[temp][i]]) q.push (G[temp][i]); } } } int main () { cin >> n; for (int i = 1 ; i <= n; ++i) { cin >> index; cin >> t[index]; while (cin >> x) { if (x == 0 ) break ; G[x].push_back (index); ++indegree[index]; } } Topo_sort (); for (int i = 1 ; i <= n; ++i) ans = max (ans, f[i]); cout << ans << endl; return 0 ; }
DAG判环:只需要新建一个cnt变量来记录队列中pop出来的顶点的个数,设总顶点数为N,若cnt==N,则表明无环,若cnt!=N,则表示有环。
拓扑排序的稳定性
拓扑排序时,若每一次入队的顶点数量均为1,则代表拓扑排序的结果只有一个,排序是稳定的;若每一次入队的顶点的数量不为1,则表示同一阶段有多个入度为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 int n = 1005 ; vector<int > father = vector <int > (n, 0 ); void init () { for (int i = 0 ; i < n; ++i) { father[i] = i; } } int find (int u) { return u == father[u] ? u : father[u] = find (father[u]); } bool isSame (int u, int v) { u = find (u); v = find (v); return u == v; } void join (int u, int v) { u = find (u); v = find (v); if (u == v) return ; father[v] = u; }
最短路径 Dijkstra算法(处理单源最短路径) Dijkstra算法用来解决单源最短路径问题,即给定一个图G和起点s,通过算法求出点s到达图中其他顶点的最短路径。其基本思想是对图G(V,E)设置集合S,存放已访问的顶点,然后每次从V-S中选择与顶点s最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化起点s与所有从u所能到达的顶点v之间的最短距离。这样执行n次(n为顶点个数),知道集合S中包含图中所有起点。
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 <bits/stdc++.h> using namespace std ; const int N = 1005 ;int G[N][N];int n,m;int d[N];bool vis[N] = {false }; const int INF = 1000000005 ;void Dijkstra (int s) { fill(d,d+N,INF); d[s] = 0 ; for (int i=1 ;i<=n;++i) { int u = -1 ,MIN = INF; for (int j=1 ;j<=n;++j) { if (vis[j]==false && d[j] < MIN) { u = j; MIN = d[j]; } } if (u == -1 )return ; vis[u] = true ; for (int v=1 ;v<=n;++v) { if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) { d[v] = d[u] + G[u][v]; } } } } int main () { cin >>n>>m; for ( int i=1 ;i<=n;++i){ for (int j=1 ;j<=n;++j) { G[i][j] = INF; } } while (m--) { int u,v,w; cin >>u>>v>>w; G[u][v] = w; } Dijkstra(1 ); if (d[n] == INF)cout <<-1 <<endl ; else cout <<d[n]<<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 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <vector> #include <cstring> using namespace std ; const int N=100010 ;#define INF 0x3f3f3f3f int n,m;int d[N];bool vis[N]={false };struct Node { int v; int weight; }; void init () { memset (d,INF,sizeof (d)); memset (vis,false ,sizeof (vis)); } vector <Node>Adj[N]; void Dijkstra (int s) { fill(d,d+N,INF); d[s]=0 ; for (int i=1 ;i<=n;i++) { int u=-1 ,MIN=INF; for (int j=1 ;j<=n;j++) { if (vis[j]==false &&d[j]<MIN) { u=j; MIN=d[j]; } } if (u==-1 )return ; vis[u]=true ; for (int j=0 ;j<Adj[u].size();j++) { int v=Adj[u][j].v; if (vis[v]==false &&Adj[u][j].weight+d[u]<d[v]) { d[v]=d[u]+Adj[u][j].weight; } } } } int main () { cin >>n>>m; for (int i=1 ;i<=m;i++) { int x,y; cin >>x>>y; Node nd; nd.v=y;nd.weight=1 ; Adj[x].push_back(nd); } for (int i=1 ;i<=n;i++) { Dijkstra(i); int maxn=0 ; for (int j=1 ;j<=n;j++) { if (d[j]!=INF) { maxn=max(j,maxn); } } cout <<maxn<<' ' ; init(); } 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 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <queue> using namespace std;const int N=100010 ;const int INF=0x3f3f3f3f ;int n,m,s,u,v,w;int d[N];bool vis[N]= {false }; struct Node { int v; int weight; Node (int a,int b) { v=a; weight=b; } }; vector<Node>Adj[N]; struct cmp { bool operator () (Node n1,Node n2) { if (n1.weight==n2.weight) { return n1.v>n2.v; } else return n1.weight>n2.weight; } }; void Dijkstra (int s) { fill (d,d+N,INF); d[s]=0 ; priority_queue<Node,vector<Node>,cmp>q; Node n1 (s,0 ) ; q.push (n1); while (!q.empty ()) { Node M=q.top (); q.pop (); if (vis[M.v]==true )continue ; if (d[M.v]==INF)break ; vis[M.v]=true ; int v=M.v; for (int w = 0 ; w < Adj[v].size (); ++w) { if (d[Adj[v][w].v] > d[v] + Adj[v][w].weight) { d[Adj[v][w].v] = d[v] + Adj[v][w].weight; Node K (Adj[v][w].v, d[Adj[v][w].v]) ; q.push (K); } } } } int main () { cin>>n>>m>>s; for (int i=0 ;i<m;i++) { int x,y,z; cin>>x>>y>>z; Node temp (y,z) ; Adj[x].push_back (temp); } Dijkstra (s); for (int i=1 ;i<=n;i++) { cout<<d[i]<<' ' ; } return 0 ; }
Floyd算法(解决全源最短路径) 该算法用于求任意两点之间的最短路径,也可以来求解一个点是否能到达另一个点。dis[][]数组用于存图。算法核心在于中转站的选择,意为在前v个中转站被允许参与中转的情况下,任意两点可以到达的最短路径,枚举中转站的时候也可以用来判断该点是否位于最短路径当中。注意先初始化dis[][]为INF。
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 #include <iostream> #include <algorithm> #include <cstdio> using namespace std ; const int N = 200 ; int dis[N][N]; int n,m; const int INF = 1000000000 ; void Floyd () { for (int k=0 ;k<n;++k) { for (int i=0 ;i<n;++i) { for (int j=0 ;j<n;++j) { if (dis[i][k] != INF && dis[k][j] != INF && dis[i][j] < dis[i][k] + dis[k][j]) { dis[i][j] = dis[i][k] + dis[k][j]; } } } } } int main () { int u,v,w; fill(dis[0 ],dis[0 ] + N * N, INF); cin >>n>>m; for (int i=0 ;i<n;++i)dis[i][i] = 0 ; while (m--) { cin >>u>>v>>w; dis[u][v] = w; } Floyd(); for (int i=0 ;i<n;++i) { for (int j=0 ;j<n;++j) { cout <<dis[i][j]<<" " ; } cout <<endl ; } return 0 ; }
求路径
Floyd算法可以多开一个path的二位数组来存放中转站标号,只需要在dp的时候在下面多加一句path[u][w]=v;即可。 在求路径的时候需要用到递归。限制条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= n; ++j) { cin >> dis[i][j]; path[i][j] = -1 ; } } for (int v = 1 ; v <= n; ++v) { for (int u = 1 ; u <= n; ++u) { for (int w = 1 ; w <= n; ++w) { if (dis[u][w] > dis[u][v] + dis[v][w]) { dis[u][w] = min(dis[u][w], dis[u][v] + dis[v][w]); } } } }
1 2 3 4 5 6 7 8 9 10 void printPath (int u, int v) { if (paht[u][v] == -1 ) { cout << "<" << u << "," << v << ">" << endl; } else { int mid = path[u][v]; printPath (u, mid); printPath (mid, v); } }
Bellman-Ford算法 Bellman-Ford算法使用于求解单源最短路,该算法可以允许负权值边的存在。Bellman-Ford算法算法思想为进行n - 1次松弛操作,每一次松弛操作都枚举每一条边,对该边的两端顶点路径长度进行修改。以此求出最短路径,但是无法解决存在负权回路的情况,因此还可以检测是否包含负权回路。 时间复杂度为O(nm),其中n为顶点数,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 #include <bits/stdc++.h> using namespace std;const int N = 1e4 +10 ; const int M = 1e4 +10 ; int dis[N], u[M], v[M], w[M]; int n,m; int cnt = 1 ; void Bellman_Ford (int s) { memset (dis,0x3f ,sizeof (dis)); dis[s] = 0 ; for (int i=1 ;i<=n-1 ;++i) { int check = 0 ; for (int j=1 ;j<=cnt;++j) { if (dis[u[j]] + w[j] < dis[v[j]]) { dis[v[j]] = dis[u[j]] + w[j]; check = 1 ; } } if (check == 0 )break ; } } int main () { cin>>n>>m; for (int i=1 ;i<=m;++i) { int x,y,z; cin>>x>>y>>z; u[cnt] = x, v[cnt] = y, w[cnt] = z; cnt++; u[cnt] = y, v[cnt] = x, w[cnt] = z; cnt++; } Bellman_Ford (1 ); for (int i=1 ;i<=n;++i) { cout<<dis[i]<<" " ; } return 0 ; }
SPFA算法 SPFA(Shortest Path Faster Algorithm)算法,是Bellman-Ford算法的队列优化版,时间复杂度较为玄学,O(km),k为常数,平均值为2。理论上讲SPFA可以对Bellman-Ford进行常数级别的优化,但是在算法竞赛当中可能出现卡SPFA时间复杂度使其时间复杂度退化为O(nm)的情况,对于不存在负权值边的图来讲,Dijkstra算法在优先队列优化过后效果稳定且时间复杂度优秀,优先选用Dijkstra。但是对于存在负权值边的图来讲,Dijkstra算法会失效,所以还得使用SPFA。
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 const int maxn = 1e5 + 5 ;const int INF = 0x3f3f3f3f ;int n, m, s, a, b, w;struct node { node (int v, int w) { vertex = v, weight = w; } int vertex, weight; }; vector<node> G[maxn]; int dis[maxn]; bool mark[maxn]; void SPFA (int start) { for (int i = 1 ; i <= n; ++i) dis[i] = INF; dis[start] = 0 ; queue<int > q; q.push (start); mark[start] = 1 ; while (!q.empty ()) { int v = q.front (); q.pop (); mark[v] = 0 ; for (int w = 0 ; w < G[v].size (); ++w) { if (dis[G[v][w].vertex] > dis[v] + G[v][w].weight) { dis[G[v][w].vertex] = dis[v] + G[v][w].weight; if (!mark[G[v][w].vertex]) { mark[G[v][w].vertex] = 1 ; q.push (G[v][w].vertex); } } } } }
SPFA算法判断是否存在负环 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 int n; int h[N], w[N], e[N], ne[N], idx; int dist[N], cnt[N]; bool st[N]; bool spfa () {一定有两个点相同,所以存在环。 queue<int > q; for (int i = 1 ; i <= n; i ++ ){ q.push (i); st[i] = true ; } while (q.size ()){ auto 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 ;}
最小生成树 Prim算法 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 const int maxn = 5005 ;const int INF = 0x3f3f3f3f ;struct node { node (int v, int w) { vertex = v, weight = w; } int vertex, weight; }; struct cmp { bool operator () (node n1, node n2) { return n1.weight > n2.weight; } }; int n, m, a, b, w, ans;bool mark[maxn];int dis[maxn]; vector<node> G[maxn]; void Prim (int start) { for (int i = 1 ; i <= n; ++i) dis[i] = INF; dis[start] = 0 ; priority_queue<node, vector<node>, cmp> q; node N (start, 0 ) ; q.push (N); while (!q.empty ()) { node M = q.top (); q.pop (); if (mark[M.vertex]) continue ; if (dis[M.vertex] == INF) break ; mark[M.vertex] = 1 ; int v = M.vertex; for (int i = 0 ; i < G[v].size (); ++i) { if (dis[G[v][i].vertex] > G[v][i].weight && !mark[G[v][i].vertex]) { dis[G[v][i].vertex] = G[v][i].weight; node P (G[v][i].vertex, G[v][i].weight) ; q.push (P); } } } }
Kruskal算法 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 const int maxn = 5005 ;const int INF = 0x3f3f3f3f ;struct edge { edge (int _u, int _v, int _w) { u = _u, v = _v, w = _w; } int u, v, w; }; bool cmp (edge e1, edge e2) { return e1.w < e2.w; } int n, m, a, b, w, cnt, ans;vector<edge> E; int father[maxn]; int _find(int s) { while (father[s] != s) s = father[s]; return s; } void merge (int s1, int s2) { int f1 = _find(s1), f2 = _find(s2); father[max (f1, f2)] = father[min (f1, f2)]; } void Kruskal () { for (int i = 1 ; i <= n; ++i) father[i] = i; sort (E.begin (), E.end (), cmp); for (int i = 0 ; i < E.size (); ++i) { int _u = E[i].u, _v = E[i].v, _w = E[i].w; if (_find(_u) != _find(_v)) { cnt++; ans += _w; merge (_u, _v); } if (cnt == n - 1 ) break ; } }
Tarjan算法 强连通分量(Strongly Connected Components)
若有向图中有两个点 i 与 j 可以相互到达,则称这两个点强连通,如果图中任意两个点都强连通,则该图称为强连通图。任意一个点自己和自己是强连通的。
非强连通有向图的极大强连通子图称为该图的强连通分量。
根据定义,两个点一定是强连通的,当且仅当它们在同一个环内。环上所有的点都互相强连通。
算法思路
该算法有两个数组比较重要,第一个是时间戳数组dfn[],该数组是用来记录对应节点第一次被访问的顺序。另一个是追溯值数组low[],该数组表示了从对应节点出发,所能够访问到的最早时间戳,以便方便我们进行强连通分量的判断。
算法分三步:
入:指从 x 节点发起Tarjan算法时,记录 x 对应的时间戳,并将 x 入栈。
回:我们对 x 发起Tarjan算法,对 x 的子节点 y 进行遍历,分以下三种情况:
如果 y 还未被访问,则继续对 y 进行深搜。回溯到 x 的时候,我们需要利用 y 的low值来更新 x 的low值。
如果 y 已经被访问并且 y 在栈中,说明了 y 是 x 的祖先节点或者左子树节点,这个时候我们直接利用 y 的dfn值来更新 x 的low值。
如果 y 已经访问并且不在栈中,表示 y 已经是属于另一个强连通分量,不需要对其进行其他处理了。
离:在处理完 x 之后,判断 x 是否为一个强连通分量的入口,如果是,则出栈,并且记录相对应的强连通分量。
根据算法过程容易注意到,因为回溯,所以越往后搜索到的点强连通分量编号越靠前。
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 #include <iostream> #include <vector> using namespace std;const int maxn = 1e4 + 5 ;int n, m, a, b, ans;vector<int > G[maxn]; bool instk[maxn]; int stk[maxn], top; int dfn[maxn], low[maxn], tot; int scc[maxn], siz[maxn], cnt; void Tarjan (int x) { dfn[x] = low[x] = ++tot; stk[++top] = x, instk[x] = 1 ; for (int i = 0 ; i < G[x].size (); ++i) { int y = G[x][i]; if (!dfn[y]) { Tarjan (y); low[x] = min (low[x], low[y]); } else if (instk[y]) { low[x] = min (low[x], dfn[y]); } } if (dfn[x] == low[x]) { int tmp; ++cnt; do { tmp = stk[top--]; instk[tmp] = 0 ; scc[tmp] = cnt; ++siz[cnt]; } while (tmp != x); } } int main () { cin >> n >> m; for (int i = 1 ; i <= m; ++i) { cin >> a >> b; G[a].push_back (b); } for (int i = 1 ; i <= n; ++i) { if (!dfn[i]) Tarjan (i); } for (int i = 1 ; i <= cnt; ++i) { if (siz[i] > 1 ) ++ans; } cout << ans; return 0 ; }
Tarjan算法缩点 Tarjan算法的缩点一般是在利用Tarjan算法求出SCC之后进行的操作,通常是对一个节点 i 访问它的子节点 j ,而后判断两个节点是否属于同一个SCC,如果不属于同一个SCC,则记录相应的入度出度,或者直接建新图。
1 2 3 4 5 6 7 8 9 for (int i = 1 ; i <= n; ++i) { for (int j = 0 ; j < G[i].size (); ++j) { if (scc[i] != scc[G[i][j]]) { din[scc[G[i][j]]]++; dout[scc[i]]++; } } }
1 2 3 4 5 6 7 for (int i = 1 ; i <= n; ++i) { for (int j = 0 ; j < G[i].size (); ++j) { if (scc[i] != scc[G[i][j]]) new_G[scc[i]].push_back (scc[G[i][j]]); } }
Hierholzer算法 又称插入回路法,用于求解欧拉路和欧拉路径。
时间复杂度O(n+m),空间复杂度O(n),n为顶点数,m为边数。
求解欧拉路的时候需要提前判明该图是否存在欧拉路。判定条件如下:
有向图:
欧拉回路:所有顶点出度入度一致。
欧拉路径:恰好有一个点的出度比入度多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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 const int maxn = 1000005 ;int n, m, u, v, start = 1 ;bool flag = true ; int indegree[maxn], outdegree[maxn]; vector<int > G[maxn]; stack<int > ans; void judge_path () { int cnt1 = 0 , cnt2 = 0 ; for (int i = 1 ; i <= n; ++i) { if (indegree[i] == outdegree[i]) continue ; if (indegree[i] + 1 == outdegree[i]) { cnt1++; start = i; } else if (indegree[i] == outdegree[i] + 1 ) cnt2++; else { flag = false ; break ; } } if (!((cnt1 == 1 && cnt2 == 1 ) || (cnt1 == 0 && cnt2 == 0 ))) flag = false ; } void dfs (int s) { for (vector<int >::iterator it = G[s].begin (); it != G[s].end ();) { int next = *it; it = G[s].erase (it); dfs (next); } ans.push (s); } int main () { cin >> n >> m; for (int i = 1 ; i <= m; ++i) { cin >> u >> v; G[u].push_back (v); ++outdegree[u]; ++indegree[v]; } juede_path (); if (!flag) { cout << "No" << endl; } else { for (int i = 1 ; i <= n; ++i) { sort (G[i].begin (), G[i].end ()); } dfs (start); } while (!ans.empty ()) { int temp = ans.top (); cout << temp << ' ' ; ans.pop (); } 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 const int maxn = 10005 ;int n, m, a, b, start = 1 ;bool flag = true ; int degree[maxn], father[maxn]; vector<int > G[maxn]; stack<int > ans; struct edge { edge (int _u, int _v) { u = _u, v = _v; } bool operator <(edge e1)const { if (u == e1.u) return v < e1.v; return u < e1.u; } int u, v; }; set<edge> E; int _find(int s) { while (father[s] != s) s = father[s]; return s; } void _merge(int s1, int s2) { int f1 = _find(s1), f2 = _find(s2); father[max (f1, f2)] = father[min (f1, f2)]; } void judge_path () { int cnt = 0 ; for (int i = 1 ; i <= n; ++i) { father[i] = i; if (degree[i] & 1 ) { cnt++; start = i; } } for (set<edge>::iterator it = E.begin (); it != E.end (); it++) { edge tmp = *it; _merge(tmp.u, tmp.v); } for (int i = 1 ; i <= n; ++i) { int f = _find(i); if (f != 1 ) { flag = false ; break ; } } if (!(cnt == 0 || cnt == 2 )) flag = false ; } void dfs (int start) { for (int i = 0 ; i < G[start].size (); ++i) { edge _find(min (start, G[start][i]), max (start, G[start][i])); set<edge>::iterator it = E.find (_find); if (it != E.end ()) { E.erase (it); dfs (G[start][i]); } } ans.push (start); } int main () { cin >> n >> m; for (int i = 0 ; i < m; ++i) { cin >> a >> b; G[a].push_back (b); G[b].push_back (a); E.insert (edge (min (a, b), max (a, b))); degree[a]++; degree[b]++; } judge_path (); if (!flag) { cout << "-1" << '\n' ; } else { for (int i = 1 ; i <= n; ++i) { sort (G[i].begin (), G[i].end ()); } dfs (start); while (!ans.empty ()) { int temp = ans.top (); cout << temp << ' ' ; ans.pop (); } } return 0 ; }