算法录 之 拓扑排序和欧拉路径。
1:
问题:
对于一个DAG(有向无环图),求点的排序,使得排在后面的点不能通过一条路径到前面的点。
如图,他的其中一个拓扑排序是 1,2,3,4,5,但是1,2,5,3,4不行,因为3能到5。
求解算法:
可以看出如果某个点有入边,也就是有其他的点能够到这个点,那么显然这个点不能被放在开头。所以就需要先找到一个入度为0的点放在开头,然后删掉这个点和这个点连接的所有的边,然后对剩下的图递归求解就OK了。
然后实现方法类似BFS,把所有入度为0的点加入队列,然后每次取出队首的点放入答案数组中,然后删掉所有从这个点出发的边后,把新产生的入度为0的点加入。
伪代码如下:
1 vector <int> Rank() { 2 Queue que; 3 vector <int> ans; 4 5 for(int i=1;i<=N;++i) 6 if(degree(i)==0) que.push(i); 7 8 while(!que.empty()) { 9 int t=que.first(); 10 que.pop(); 11 12 ans.push_back(t); 13 14 for(所有从t出发的边e) { 15 --degree(e.to); // 删除这条边并且让这条边到达的那个点的入度减一。 16 if(degree(e.to)==0) que.push(e.to); 17 } 18 } 19 20 return ans; 21 }
复杂度的话,因为每个点最多遍历一次,每个点的每条边也是。
所以复杂度是 N+M (M是边数)。
2:
问题:
求一个无向图的一条欧拉路?也就是是否可以一笔画完?
先判断图是否存在欧拉路,不存在直接返回。根据欧拉的定理:如果一个无向图有三个或者三个以上的点的度数是奇数,就没有欧拉路。
先找到degree为奇数的一个点(不存在的话就任意点)作为开始点,进行DFS,每次走过一条边之后就删除这条边。然后当某个点无路可走的时候,把这个点加入答案数组。
最后答案数组反向就是答案。
伪代码如下:
1 vector <int> ans; 2 3 void dfs(int u) { 4 if(u没有路可以继续下去) ans.push_back(u); 5 else { 6 for(e 是 u 出发的边) { 7 delete e; 8 dfs(e.to); 9 } 10 } 11 } 12 13 void getEuler() { 14 int start; 15 for(start=1;start<=N;++start) 16 if(degree(start)%2==1) 17 break; 18 if(start==N+1) start=1; 19 20 dfs(start); 21 ans.reverse(); // ans反向。 22 }
例图如下:
从1开始dfs,找到1-2的边,删除,dfs 点2 然后找到边2-3,删除,dfs 点3
然后找到边1-3,删除,dfs 点1。 这时dfs 点1无路可走,把1加入ans。然后回溯到3, 然后找到边4-5,删除,dfs 点5。
找到边3-4,删除,dfs 点4。
然后找到边5-3,删除,dfs 点3 然后3无路可走,加入ans,回溯到5,然后5无路可走,加入ans,回溯到4,然后4无路可走,加入ans,回溯到3,然后3无
路可走,加入ans,回溯到2,然后2无路可走,加入ans,回溯到1,然后1无路可走,加入ans。
这时ans数组是 1,3,5,4,3,2,1。
倒序的话就是一条欧拉路。
至于正确性的证明,严密的证明不会,但是如果一个点边很少的话一定要尽量往后被访问,不然进去就出不来了。所以这算是半个贪心策略,嗯。