欧拉回路:从图上一点出发,经过所有的边必须且只能一次,最终回到起点的路径欧拉图:有欧拉回路的图
欧拉图要满足两个条件:
- 连通,不能有孤立的点存在,即没有度数为0的点
- 对于无向图来说,度数为奇数的点的个数为0;对于有向图来说,每个点的入度要等于出度
欧拉图的变形—— “一笔画问题” (witness手滑):可以不回到原点,但依然要经过所有的边必须且只能一次,这样同样要满足两个条件:
- 连通,同上
- 对于无向图来说,度数为奇数的点的个数为2,且这两个点一定是一笔画的起点和终点;对于有向图来说,存在两个顶点,其入度不等于出度,其中一点出度比入度大1,为路径的起点,另外一点入度比出度大1,为路径的终点。
求欧拉回路的算法:直接对边进行dfs,回溯的顺序就是欧拉回路由于一笔画可以通过预处理确定起点和终点,那么直接对起点或终点进行边dfs即可
求边的序列的话,在dfs回溯之后把边加入求点的序列是在每次dfs函数结束之前把点加入
总的来说欧拉回路是一个比较傻逼的东西,但我之前居然不会。。。看来还是要快点把书上的东西过一遍了。
哈密顿回路:从图上一点出发,经过所有的点必须且只能一次,最终回到起点的路径哈密顿图:存在哈密顿回路的图
哈密顿图的判断是一个典型的NPC问题,但有两个判定条件被证明是对的:
- 充分条件:设图G是具有n个顶点的无向连通图,如果G中任意两个不同顶点的度数之和大于等于n,则G具有哈密顿回路,即G是哈密顿图。
- 必要条件:设图G是哈密顿图,则对于该图的任意一个非空点集S,W(G-S)<=|S|都成立,其中G-S为G去掉S中的点和与它们相连的边,W(G-S)就是剩下的东西的连通分量的个数,|S|就是S中点的个数。
在图G满足第一个条件的情况下,有构造哈密顿回路的算法:
先找两个相邻的点,然后两边一直伸长到不能在伸为止,就得到一条路径,如果此时路径两端点之间也存在边的话就是一个回路如果不相连的话,根据第一个条件和鸽巢原理,可以知道两端点间必存在两个相邻的点,分别与两端点有边相连,那么重新安排一下还是可以得到一个回路得到一个回路之后,如果回路上的点为N则已经求出哈密顿回路,否则在这个回路中找出有边与剩余的点相连的点,然后在这个点处随便一边把回路断开,让这个点继续往外扩展,这就回到了第一步,一直重复到回路上的点数为N为止。
模板代码:(由于时间复杂度是n^2的,所以用邻接矩阵来存储也无所谓了,判断两点是否相连时还更快一些)
int ans[maxn]; bool con[maxn][maxn]; bool vis[maxn]; inline void reverse(int ans[maxn],int s,int t) { int temp; while(s<t) { swap(ans[s],ans[t]); s++;t--; } } inline int extend(int ans[maxn],int &cur) { while(1) { bool flag=1; rep(i,1,n) { if(con[cur][i] && !vis[i]) { ans[cur++]=i; vis[i]=1; flag=0; break; } } if(flag)break; } return ans[cur-1]; } void Hamilton(int ans[maxn],bool con[maxn][maxn],int n) { int s=1,t; int ansi=2; int i,j,w,temp; clr(vis,0); rep(i,1,n)if(con[s][i])break; t=i; vis[s]=vis[t]=1; ans[0]=s;ans[1]=t; while(1) { t=extend(ans,ansi); reverse(ans,0,ansi-1); swap(s,t); t=extend(ans,ansi); if(!con[s][t]) { w=ansi-1; for(int i=1;i<w;i++) { if(con[ans[i]][t] && con[ans[i+1]][s]) { reverse(ans,i+1,w); t=ans[w]; break; } } } if(ansi==n)return; rep(i,1,n)if(!vis[i]) { for(j=0;j<ansi;j++)if(con[ans[j]][i])break; if(j<ansi) { vis[i]=1; reverse(ans,j,ansi-1); reverse(ans,0,j-1); ans[ansi++]=i; break; } } } }