欧拉回路(详解)

  • 欧拉通路和欧拉回路:
    欧拉通路:对于图G来说,如果存在一条通路包含G的所有边,则该通路称为欧拉通路,也称欧拉路径。
    欧拉回路:如果欧拉路径是一条回路,那么称其为欧拉回路。
    欧拉图:含有欧拉回路的图是欧拉图。

  • 对有向图G和无向图H:
    图G存在欧拉路径与欧拉回路的充要条件分别是:
    欧拉路径:图中所有奇度点的数量为0或2.
    欧拉回路:图中所有点的度数都是偶数。

    图H存在欧拉路径和欧拉回路的充要条件分别为:
    欧拉路径:所有点的入度等于出度或者存在一点出度比入度大1(起点),一点入度比出度大1(终点),其他点的入度均等于出度。
    欧拉回路:所有点的入度等于出度。

  • 问题

    对于一张图,经过每条边一次且仅一次的回路被称为欧拉回路。

    现在的问题是,给定一张无向图或有向图,判断是否存在欧拉回路,若存在,找出一条欧拉回路。

    基本算法

    先考虑无向图的情况。

    下面的讨论中,假设图中不存在孤立点(度为 0 的点)。因为将孤立点从图中删除不会影响欧拉回路的存在性。

    有结论:无向图 G 存在欧拉回路,当且仅当图 G 连通且所有顶点度数为偶数。

    证明:

    必要性:对于一条欧拉回路 C ,由于 C经过图 G 的所有边,因此 C 经过图 G 的所有点,因此图 G 连通。

    因为 C 是回路,因此 C中跟每个点相连的边数是偶数,由于每条边经过且只被经过一次,因此图 G 每个点的度数为偶数。

    充分性:

    即证明若图 G 连通且所有顶点度数为偶数,则 G 存在欧拉回路。

    归纳证明。

    假设结论对比 G 的子图都成立,现在证明对 G 成立。

    由于图 G连通且所有顶点度数为偶数,因此一定存在一条回路。

    因为从一个任意一点出发,每次任意选一条没走过的边走,那么一定会走到一个已经走过的点,这时就找到了一条回路。

    假设 C 是图 G 的一条回路。将 C 从图 G 中删除,得到图 G′ ,那么对于 G′ 的每个连通块,必定满足所有点度数为偶数,因此都存在欧拉回路。

    对于两条存在公共点的回路,我们可以合并他们得到一条新的回路。因为对于一条回路可以从回路上任意一点出发走完这条回路然后回到自己,那么只需从公共点出发走完第一条回路再走完第二条回路就得到了他们的合并回路。

    因此只需将 C与 G′ 的每个连通块的欧拉回路依次合并就得到了 G 的欧拉回路。

    证毕。

    同时由此可以得到求图 G 的欧拉回路的算法:

  • 从图 G 中任意找一条回路 C ;
  • 将图 G 中属于 C 的边删除,得到 G′ ;
  • 在 G′ 的各连通块递归寻找欧拉回路;
  • 将 G'的各连通块的欧拉回路与 C 合并得到图 G 的欧拉回路。
  • 题目链接  欧拉回路 - 题目 - Universal Online Judge
  • AC 代码:
  • #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    const int N = 100100, M = 400100;
    
    int h[N],e[M],ne[M],idx;
    int ans[N*2],cnt;
    bool used[M];
    int din[N],dout[N];
    int n,m,ver;
    
    void add(int a,int b){
        e[idx] = b,ne[idx] = h[a],h[a] = idx++;
    }
    
    void dfs(int u){
        for(int &i = h[u]; ~i; ){
            if(used[i]){  //如果这条边用过了
                i = ne[i];   //删除这条边
                continue;
            }
    
            used[i] = true;  //标记这条边已使用
            if(ver == 1) used[i^1] = true;   //如果是无向图,那么这条边的反向边也要标记使用过了
    
            int t;
            if(ver == 1){
                t = i/2 + 1;
                if(i&1) t = -t;  //(0,1) (2,3) (4,5) 奇数编号是返回的边
    
            }else t = i+1;
    
            int j = e[i];
            i = ne[i];
            dfs(j);
            ans[cnt++] = t;
        }
    }
    int main()
    {
        scanf("%d%d%d",&ver,&n,&m);
        memset(h,-1,sizeof h);
    
        for(int i = 0; i=0; --i){
            cout<

你可能感兴趣的:(c++,算法,图论)