图算法专题 图的存储 
邻接矩阵可以采用一个二维数组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 ; }