欧拉路问题,俗称“一笔画”问题
给定一张无向图。若存在一条从节点S到节点T的路径,恰好不漏不重的经过每一条边一次(可以重复经过节点),则称该路径为S到T的欧拉路若存在一条从节点S出发,恰好不漏不重地经过每一条边(可以重复经过图中节点)最终回到起点S,则该路径称为欧拉回路。
存在欧拉回路的无向图称为欧拉图欧拉图判定
一张无向图为欧拉图,当且仅当无向图联通,且每个节点的度都是偶数欧拉路存在性判定
一张无向图存在欧拉路,当且仅当无向图连通,并且图中恰好有两个节点的度数为奇数,其他节点的度数为偶数。这两个度数为奇数的节点就是起点S和终点T欧拉回路的方案
在保证一张无向图时欧拉图时
欧拉图每个节点度数为偶数说明:只要到达一个节点,就必定存在有一条尚未走过的边可以离开该点。故在伪代码中调用dfs(1),不断递归,每次都走到“从x出发的第一条未访问的边”的另一端点y,最终一定能回到节点1,产生一条回路但是这条回路不能保证经过图中的每条边。所以dfs函数会继续考虑从x出发的其他未访问的边,找到第二条回路伪代码实际找出了若干条回路,我们需要把这些回路按照适当的方法拼接在一起,形成整张图的欧拉回路,一个拼接方法就是把第二条回路嵌入第一条回路中间
而伪代码中的栈,替我们完成了这个拼接工作,最后,把栈中的所有节点倒序输出,就得到了一条欧拉回路
上述算法的复杂度时O(NM)。因为一个点会被重复遍历多次,每次都会扫描与它相连的所有的边,虽然大部分的边已经访问过了
假设我们使用邻接表来存储无向图,我们可以在访问一条边(x, y)后,及时修改表头head[x],令它指向下一条边。这样我们每次只需去除head[x],就自然跳过了所有已经访问过的边因为欧拉回路的DFS的递归层数时O(M)级别,容易造成系统栈溢出。我们可以用另一个栈,模拟机器的递归过程把代码转为非递归实现最后复杂度为O(N + M)
1 #include2 using namespace std; 3 const int maxn = 500010; 4 struct shiki { 5 int y, net; 6 }e[maxn << 1]; 7 int lin[maxn], len = 0; 8 int s[maxn], ans[maxn]; 9 bool vis[maxn];10 int n, m, t_f = 0, t_s = 0;11 12 inline int read() {13 int x = 0, y = 1;14 char ch = getchar();15 while(!isdigit(ch)) {16 if(ch == '-') y = -1;17 ch = getchar();18 }19 while(isdigit(ch)) {20 x = (x << 1) + (x << 3) + ch - '0';21 ch = getchar();22 }23 return x * y;24 }25 26 inline void insert(int xx, int yy) {27 e[++len].y = yy;28 e[len].net = lin[xx];29 lin[xx] = len;30 }31 32 inline void euler() {33 s[++t_f] = 1;34 while(t_f > 0) {35 int x = s[t_f], i = lin[x];36 while(i && vis[i]) i = e[i].net;37 if(i) {38 s[++t_f] = e[i].y;39 vis[i] = vis[i ^ 1] = true;40 lin[x] = e[i].net;41 }42 else t_f--, ans[++t_s] = x;43 }44 }45 46 int main() {47 n = read(), m = read();48 len = 1;49 for(register int i = 1; i <= m; ++i) {50 int x = read(), y = read();51 insert(x, y);52 insert(y, x);53 }54 euler();55 cout << "One of all Euler circuits is : "<< ' ';56 for(register int i = t_s; i >= 1; --i)57 cout << ans[i] << ' ';58 return 0;59 }
例题:Watchcow(poj2230)
给定N个点M条边的无向图(1 <= N <= 10^4, 1 <= M <= 5 * 10^4),求一条路径,从节点1出发,最后回到节点1,并且满足每条边恰好被沿着正,反两个方向分别经过一次。
若有多种方案,输出一种即可按照一般的存储方式,无向边会在邻接表中以正、反两个方向分别被保存一次,若没有vis标记,则按照表头数组head的更新方法,每条无向边会被正反各经过一次,恰好符合题目要求
请自己动手写递归吧!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include