图论常用算法之二 算法模板及建模总结
寒假的第二周,弥补了一下图论算法。在这里做一下总结,主要针对近期学到的一些建模技巧,同时也非常感谢有朋友能够给出图论算法相关的精彩讲解或者知识链接。
算法总结:
欧拉回路问题:判断图是否存在欧拉回路或者欧拉通路,输出一条欧拉回路。
学习Fleury算法输出一条欧拉回路。
1 /* G是连通无向图,则称经过G的每条边一次并且仅一次的路径为 2 欧拉通路,起点和终点是同一个顶点称为欧拉回路,具有欧拉回路的 3 无向图为欧拉图。 4 依次有有向欧拉通路,有向欧拉回路,有向欧拉图 5 定理:连通图G仅有两个奇度结点或者无奇度结点 6 7 Fleury:在当前剩余图中,对于顶点v,选择和v关联的边走, 8 能不走桥就尽量不走桥 9 */ 10 #include<iostream> 11 #include<cstdio> 12 #include<cstring> 13 #include<cstdlib> 14 #include<cmath> 15 #include<algorithm> 16 #define inf 0x7fffffff 17 using namespace std; 18 const int maxn=222; 19 struct Stack 20 { 21 int top,node[maxn];///顶点的栈结构 22 }s; 23 int edge[maxn][maxn]; 24 int n,m;///顶点个数、边数 25 void dfs(int x) 26 { 27 s.top++; 28 s.node[s.top]=x; 29 for (int i=0 ;i<n ;i++) 30 { 31 if (edge[x][i]) 32 { 33 edge[x][i]=edge[i][x]=0; 34 dfs(i); 35 break; 36 } 37 } 38 } 39 void Fleury(int x) 40 { 41 s.top=0; 42 s.node[s.top]=x; 43 while (s.top >= 0 ) 44 { 45 int flag=0; 46 for (int i=0 ;i<n ;i++) 47 { 48 if (edge[s.node[s.top] ][i]) 49 { 50 flag=1;break; 51 } 52 } 53 if (flag==0) 54 { 55 printf("%d ",s.node[s.top]+1); 56 s.top--; 57 } 58 else 59 { 60 s.top--; 61 dfs(s.node[s.top+1]); 62 } 63 } 64 printf("\n"); 65 } 66 int main() 67 { 68 int a,b; 69 scanf("%d%d",&n,&m); 70 memset(edge,0,sizeof(edge)); 71 for (int i=0 ;i<m ;i++) 72 { 73 scanf("%d%d",&a,&b); 74 edge[a-1][b-1]=edge[b-1][a-1]=1; 75 } 76 int cnt=0,num=0,start=0; 77 for (int i=0 ;i<n ;i++) 78 { 79 cnt=0; 80 for (int j=0 ;j<n ;j++) cnt += edge[i][j]; 81 if (cnt%2) 82 { 83 start=i ;num++ ; 84 } 85 } 86 if (num==0 || num==2) Fleury(start); 87 else printf("No Euler path\n"); 88 return 0; 89 }
拓扑排序:和有向无环图的问题相关。
1 ///toposort 栈实现 2 #include<iostream> 3 #include<cstdio> 4 #include<cstring> 5 #include<cstdlib> 6 #include<cmath> 7 #include<algorithm> 8 #include<stack> 9 using namespace std; 10 const int maxn=100+10; 11 12 int n; 13 int in[maxn],degree[maxn]; 14 int an[maxn]; 15 int g[maxn][maxn]; 16 int toposort() 17 { 18 memset(in,0,sizeof(in)); 19 memcpy(in,degree,sizeof(degree)); 20 stack<int> S; 21 while (!S.empty()) S.pop() ; 22 for (int i=0 ;i<n ;i++) if (in[i]==0) S.push(i); 23 int j=0; 24 int flag=0; 25 while (!S.empty()) 26 { 27 if (S.size()>1) flag=1;///有多种排序方式,序列不确定 28 int k=S.top() ;S.pop() ; 29 an[j++]=k;///记录出栈的数字 30 for (int i=0 ;i<n ;i++) 31 { 32 if (g[k][i]) 33 { 34 if (--in[i]) S.push(i);///入度为0的结点入栈 35 } 36 } 37 } 38 if (j!=n) return 1;///不能拓扑排序,有环 39 if (flag) return 2; 40 return 3;///序列唯一 41 } 42 43 int main() 44 { 45 return 0; 46 }
无向图的割顶和桥
无向图的双联通分量、有向图的强联通分量:连通分量问题在无向图的割顶和桥的求解上略作修改
最短路、最小生成树:这两个算法应该是图论的入门算法,生成树的问题多变性相对而言没有最短路强,一些最短路问题也要对构造的图进行分层处理。
最短路:Dijkstra,Bellman_Ford,SPFA,
生成树:最小生成树,次小生成树,最小瓶颈路,最小有向生成树。
最小生成树:Prim,Kruskal
二分图匹配:这类问题的解决比较经典,同时也很巧妙,精彩。
二分图的问题:二分图最大匹配,二分图最佳完美匹配,二分图最小覆盖,二分图最大独立集,DAG的最小路径覆盖,二分图最小点权覆盖集,二分图最大点权独立集,等等。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<algorithm> 7 #include<stack> 8 #include<vector> 9 using namespace std; 10 const int maxn=100+10; 11 12 int n,m; 13 int g[maxn][maxn]; 14 int linker[maxn]; 15 int vis[maxn]; 16 17 bool dfs(int u) 18 { 19 for (int v=0 ;v<m ;v++) 20 { 21 if (g[u][v] && !vis[v]) 22 { 23 vis[v]=1; 24 if (linker[v]==-1 || dfs(linker[v])) 25 { 26 linker[v]=u; 27 return true; 28 } 29 } 30 } 31 return false; 32 } 33 34 int hungary() 35 { 36 int ret=0; 37 memset(linker,-1,sizeof(linker)); 38 for (int u=0 ;u<n ;u++) 39 { 40 memset(vis,0,sizeof(vis)); 41 if (dfs(u)) ret ++ ; 42 } 43 return ret; 44 } 45 46 int main() 47 { 48 return 0; 49 }
网络流:这是图论算法里一个重中之重,网络流算法也很多,代码不是很难理解,重在建模,题目千变万化,包括给一条有向边加上费用,上下界流量等等。
1 const int maxn = 44444; 2 const int M = 999999; 3 struct Edge 4 { 5 int to,cap,next; 6 } edge[M]; 7 int head[maxn],edgenum; 8 int n,m,from,to,vnum;///vnum : vertexnum 9 void add(int u,int v,int cap) 10 { 11 Edge E={v,cap,head[u] }; 12 edge[edgenum]=E; 13 head[u]=edgenum++; 14 15 Edge E2={u,0,head[v] }; 16 edge[edgenum]=E2; 17 head[v]=edgenum++; 18 } 19 int level[maxn]; 20 int gap[maxn]; 21 void bfs(int to) 22 { 23 memset(level,-1,sizeof(level)); 24 memset(gap,0,sizeof(gap)); 25 level[to]=0; 26 gap[level[to]]++; 27 queue<int> q; 28 q.push(to); 29 while(!q.empty()) 30 { 31 int u=q.front() ; q.pop(); 32 for (int i=head[u] ;i!=-1 ;i=edge[i].next) 33 { 34 int v=edge[i].to; 35 if (level[v] != -1) continue; 36 level[v]=level[u]+1; 37 gap[level[v]]++; 38 q.push(v); 39 } 40 } 41 } 42 int pre[maxn]; 43 int cur[maxn]; 44 int SAP(int from,int to) 45 { 46 bfs(to); 47 memset(pre,-1,sizeof(pre)); 48 memcpy(cur,head,sizeof(head)); 49 int u=pre[from]=from,flow=0,aug=inf; 50 gap[0]=vnum; 51 while(level[from]<vnum) 52 { 53 bool flag=false; 54 for (int &i=cur[u] ;i != -1 ;i=edge[i].next) 55 { 56 int v=edge[i].to; 57 if (edge[i].cap && level[u]==level[v]+1) 58 { 59 flag=true; 60 pre[v]=u; 61 u=v; 62 aug=min(aug,edge[i].cap); 63 if (v==to) 64 { 65 flow += aug; 66 for (u=pre[v] ;v != from ;v=u,u=pre[u]) 67 { 68 edge[cur[u]].cap -= aug ; 69 edge[cur[u]^1].cap += aug ; 70 } 71 aug=inf; 72 } 73 break; 74 } 75 } 76 if (flag) 77 { 78 continue; 79 } 80 int minlevel=vnum; 81 for (int i=head[u] ;i != -1 ;i=edge[i].next) 82 { 83 int v=edge[i].to; 84 if (edge[i].cap && level[v]<minlevel) 85 { 86 minlevel=level[v]; 87 cur[u]=i; 88 } 89 } 90 if (--gap[level[u]]==0) break; 91 level[u]=minlevel+1; 92 gap[level[u]]++; 93 u=pre[u]; 94 } 95 return flow; 96 }
建模总结
网络流部分模型建模方法
【多源多汇问题】:
源点和汇点有多个,流可以从任意一个源点流出,也可以流向任意一个汇点。
建模:加一个附加源点s和附加汇点t,然后从s向每个源点连一条有向边,容量无穷大;每个汇点向t连一条有向边,容量无穷大。
【结点有容量问题】:
每个结点有一个允许通过的最大流量,称为结点容量。
建模:把原图G中每个结点u拆成两个结点u1和u2,中间连一条有向边,容量为结点容量;原先流向u的边现在指向u1,从u流出的有向边改为从u2流出。
【无源汇的容量有上下界的流网络问题】:
针对边的构造 :
增加新源点from和汇点to,对于原图中任意一条边u->v,上下界流量分别为b,c,构造新图时,u->v的上界为c-b,新源点from->v上界为b,u->新汇点to上界为b。这样,就去掉了流量下界的控制。
针对点的构造 :
增加新源点from和汇点to,原图中的边u->v的上界为c-b(和上面一样),对于原图中任意一个点u,统计所有流入u点的流量下界和 in 和经过u点流出的所有流量下界和out ,如果 in > out ,那么就连一条边from->u上界为in-out,否则就连一条边u->to上界为out-in。
二分图部分建模方法
【二分图最小覆盖】
即选择尽量少的点,使每条边至少有一个端点被选中。
可以证明,最小覆盖等于最大匹配数。
【二分图最大独立集】
即选择尽量多的点,使得任意两个结点不相邻(即任意一条边的两个端点不能同时被选中)。
最大独立集和最小覆盖集是互补的,因此答案为结点数减去最小覆盖数。
【二分图最小点权覆盖集】
设有无向图G(V,E),对于任意结点u,都对应一个非负权值w,称为结点的点权。点权之和最小的点覆盖集为最小点权覆盖集。
二分图最小点权覆盖集=最小割
将原问题的图G构造为网络为N=(V,E)的最小割模型:
1,新增源点s和汇点t。
2,将图中每条边u->v加上容量为正无穷大。
3,对于出点集中每个点u,新增有向边s->u,边权为结点u的权值。
4,对于入点集中每个点v,新增有向边v->t,边权为结点v的权值。
【二分图最大点权独立集】
给出一个二分图,每个结点有一个权值,要求选出一些结点,使得这些点之间没有边相连,并且权值和最大。
刚才讲到二分图最大独立集和最小覆盖集是互补的,二分图最大点权独立集和最小点权覆盖集也是互补的。所以我们只要知道所有点权和以及最小点权覆盖集,那么最大点权独立集也就知道了。
【DAG的最少路径覆盖】
有向无环图G(V,E)的一个路径覆盖是指一个路径集合P,满足图中的每点属于且仅属于集合中的一条路径,求一个包含路径条数最少的路径覆盖。
二分图模型:
1,把每个点i拆成两点 i' 和 i'' 。
2,把边集中的弧(i,j)改为无向边(i'' , j' )。
还没有学习的部分算法:
2-SAT
带花树
最优比例生成树
斯坦纳树
接下来的任务就是通过题量对学习过的算法进行巩固及加深理解,以及学习新的图论算法。