图论 —— 图的遍历 —— 欧拉通路与欧拉回路问题

【基本概念】

  1. 欧拉通路:通过图中所有边一次且仅一次行遍所有顶点的通路
  2. 欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路
  3. 欧拉图:具有欧拉回路的图
  4. 半欧拉图:具有欧拉通路而无欧拉回路的图
  5. 奇度点:与点相连的边的数目为奇数的点
  6. 偶度点:与点相连的边的数目为偶数的点

【欧拉通路/回路的判定】

1.无向图

1)欧拉通路:图是连通的,图中只有两个奇度点,分别是欧拉通路的两个端点

对于欧拉通路,除起点、终点外,每个点如果进入,显然一定要出去,因此都是偶点

2)欧拉回路:图是连通的,点均为偶度点

对于欧拉回路,每个点进入、出去的次数相等,因此没有奇点

2.有向图

1)欧拉通路:图是连通的,除两顶点外其余点的入度等于出度,且这两个顶点中,一个顶点入度比出度大1(起点),另一个入度比出度小1(终点)

2)欧拉回路:图是连通的,图中所有点的入度等于出度。

3.Fleury 算法

Fleury 算法是用于求无向图中欧拉回路的算法,其基本思想如下:

  1. 任取图 G 中一顶点 v0,令 P0=v0
  2. 假设沿 Pi=v0e1v1e2...eivi,走到顶点 vi,按下面的方法从 E(G)-{e1,e2,...,ei} 中选择 ei+1:
    1)ei+1 与 vi 相关联
    2)除非无别的边可供选择,否则 ei+1 不应是 Gi=G-{e1,e2,...,ei} 中的桥
  3. 当 2 不能再进行时,算法停止

可以证明的是,当算法停止时,所得到简单回路 Pm=v0e1v1e2...emvm(vm=v0) 为G中的欧拉回路

int n,m;
int start;//起点
int num;//奇度顶点个数
int degree[N];//顶点的度
int path[N];//存储欧拉回路
int cnt;//欧拉回路计数器
bool G[N][N];
stack<int> S;
void dfs(int x){
    S.push(x);
    for(int i=1;i<=n;i++){
        if(G[x][i]){
            G[x][i]=false;
            G[i][x]=false;
            dfs(i);
            break;
        }
    }
}
void Fleury(int x){

    S.push(x);
    while(!S.empty()){
        bool flag=false;

        for(int i=1;i<=n;i++){
            if(G[S.top()][i]==true){ //与起点有关联的边
                flag=true;
                break;
            }
        }

        if(flag==true){ //如果有关联的边
            int temp=S.top();
            S.pop();
            dfs(temp);
        }
        else{ //如果没有有关联的边
            path[cnt++]=S.top();
            S.pop();
        }
    }
}
int main(){

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        G[x][y]=1;
        G[y][x]=1;

        degree[x]++;
        degree[y]++;
    }

    cnt=1;
    num=0;
    start=1;
    for(int i=1;i<=n;i++){
        if(degree[i]&1){//如果某点的度是奇数
            if(num==0)//记录起点
                start=i;
            num++;//奇度点个数+1
        }
    }

    if(num==0||num==2){//如果存在奇度顶点,则从奇度顶点出发,否则从顶点0出发
        Fleury(start);
        for(int i=cnt-1;i>=1;i--)
            printf("%d ",path[i]);
        printf("\n");
    }
    else
        printf("No Euler path.\n");

    return 0;
}

4.并查集判断无向图中是否存在欧拉回路

当给出一个无向图时,若要求判断图中是否存在欧拉回路,可以使用并查集判断图是否连通,并统计每个节点的度数,依次来判断是否存在。

若图连通且所有点的度数为偶数,则说明该无向图中存在欧拉回路。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1001
using namespace std;
int n,m;
int degree[N];
int father[N];
int Find(int x){
    if(father[x]==-1)
        return x;
    return father[x]=Find(father[x]);
}
void Union(int x,int y){
    x=Find(x);
    y=Find(y);
    if(x!=y)
        father[x]=y;
}
int main(){

    while(scanf("%d%d",&n,&m)!=EOF){
        memset(degree,0,sizeof(degree));
        memset(father,-1,sizeof(father));

        for(int i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            degree[x]++;
            degree[y]++;
            Union(x,y);
        }

        int cnt=0;//记录连通分量
        for(int i=1;i<=n;i++)
            if(Find(i)==i)
                cnt++;
        if(cnt!=1)//若cnt大于1,说明图不连通
            printf("NO\n");
        else{
            int num=0;//统计度数为奇数的点
            for(int i=1;i<=n;i++)
                if(degree[i]&1)
                    num++;
            if(num==0)
                printf("YES\n");
            else
                printf("NO\n");
        }

    }
    return 0;
}

【应用】

对于一般的单词首尾相连的问题,通常都是转化为有向图的欧拉通路问题。

例如:给出多个单词,问能不能将所有单词组成一个序列,序列的前一个单词的尾字母与后一个单词的头字母相同

如果把每个单词看出无向的边,那么最终求出的欧拉通路可能存在两个单词尾部和尾部相连的情况。

1.无向欧拉图打印欧拉通路/回路

输入保证一个有 n 个点,m 条边的具有欧拉回路或欧拉路径的无向图,要求打印出图的欧拉回路或通路。

如果要打印欧拉通路,输入的 start 一定要是起点之一,即:deg(start)=1,否则只是乱序打印图中所有的边。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#define N 1001
using namespace std;
int n,m;
int G[N][N];
bool vis[N][N];//vis[i][j]=1表示点i到j之间存在一条边
void euler(int x){//打印以x为起点的欧拉回路或通路
    for(int y=1;y<=n;y++){
        if(vis[x][y]||vis[y][x]){
            vis[x][y]=0;//去掉x-y这条边
            vis[y][x]=0;//去掉y-x这条边
            euler(y);

            //首尾相连逆序打印欧拉通路
            //printf("%d %d\n",x,y);
        }
    }

    //逆序打印欧拉回路
    printf("%d ",x);
}
int main(){

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        G[x][y]=1;
        G[y][x]=1;

        vis[x][y]=1;
        vis[y][x]=1;
    }

    int start;
    scanf("%d",&start);
    euler(start);//打印以x为起点的欧拉回路或通路

    return 0;
}

2.有向欧拉图打印欧拉通路/回路

输入保证是一 n 个点,m 条边的具有欧拉回路或欧拉路径的有向图,要求打印出图的欧拉回路或通路。

如果要打印欧拉通路,那么输入的 start 一定要是起点之一,即:|deg^+(start)-deg^-(start)|=1,否则只是乱序打印图中所有的边。

1)邻接矩阵实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#define N 1001
using namespace std;
int n,m;
int G[N][N];
bool vis[N][N];//vis[i][j]=1表示点i到j之间存在一条边
void euler(int x){//打印以x为起点的欧拉回路或通路
    for(int y=1;y<=n;y++){
        if(vis[x][y]){
            vis[x][y]=0;//去掉x-y这条边
            euler(y);

            //首尾相连逆序打印欧拉通路
            //printf("%d %d\n",x,y);
        }
    }

    //逆序打印欧拉回路
    printf("%d ",x);
}
int main(){

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        G[x][y]=1;

        vis[x][y]=1;
    }

    int start;
    scanf("%d",&start);
    euler(start);//打印以x为起点的欧拉回路或通路

    return 0;
}

2)邻接链表实现

struct Edge{
    int endd;
    bool vis;
    Edge(int endd,bool vis):endd(endd),vis(vis){}
};
int n,m;
vector<Edge> G[N];
void euler(int x) {
    for(int i=G[x].size()-1; i>=0;i--) {
        if (!G[x][i].vis){
            G[x][i].vis = 1;
            euler(G[x][i].endd);
        }
    }
    printf("%d\n",x);
}

int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        G[x].push_back(Edge(y,false));
        G[y].push_back(Edge(x,false));
    }
    int start=1;
    euler(start);
    return 0;
}

 

  • 15
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
欧拉通路(Eulerian path)是指一条路径,经过中每个边恰好一次。在基因组学中,欧拉通路可以用于确定DNA序列,即通过构建De Bruijn,找到其中的欧拉通路,然后将欧拉通路上的k-mer拼接起来,即可得到完整的DNA序列。 确定欧拉通路的方法可以使用Fleury算法或Hierholzer算法。这两种算法都可以在有向中找到欧拉通路。 以下是使用Python实现Hierholzer算法来确定DNA序列的示例代码: ```python from collections import defaultdict def find_eulerian_path(graph): """ 寻找欧拉通路 :param graph: De Bruijn :return: 欧拉通路 """ # 计算每个节点的入度和出度 in_degrees = defaultdict(int) out_degrees = defaultdict(int) for node in graph: out_degrees[node] = len(graph[node]) for neighbor in graph[node]: in_degrees[neighbor] += 1 # 选择起点 start_node = list(graph.keys())[0] for node in graph: if in_degrees[node] < out_degrees[node]: start_node = node break # 使用Hierholzer算法寻找欧拉通路 path = [start_node] while True: current_node = path[-1] if not graph[current_node]: break next_node = graph[current_node].pop(0) path.append(next_node) return "".join(path) # 示例 graph = {'AT': ['TG'], 'TG': ['GC', 'CG'], 'GC': ['CG'], 'CG': ['GC', 'GA', 'AT'], 'GA': ['AT'], 'AA': ['AT'], 'TC': ['CG', 'CG'], 'AA': ['AT'], 'AT': ['TC']} path = find_eulerian_path(graph) print(path) ``` 输出结果为: ``` ATGCGCGATCGAATCG ``` 该代码先计算De Bruijn中每个节点的入度和出度,然后选择一个入度小于出度的节点作为起点,使用Hierholzer算法寻找欧拉通路,最后将欧拉通路上的k-mer拼接起来,得到完整的DNA序列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值