欧拉回路学习笔记 + 例题

欧 拉 回 路 欧拉回路

定义:

  1. 欧拉路径:如果图G中的一个路径包括每个边恰好一次,则该路径称为欧拉路径(Euler path)。
  2. 欧拉回路:如果一个回路是欧拉路径,则称为欧拉回路(Euler circuit)。(起点和终点相同的欧拉路径)
  3. 半欧拉图:具有欧拉路径但不具有欧拉回路的图称为半欧拉图。
  4. 欧拉图:具有欧拉回路的图称为欧拉图(简称E图)。

欧拉回路的存在条件:

首先图必定是连通的!!(这个就不用解释了)
图的连通性可以通过并查集或者DFS判断

对于无向图:

当且仅当该图所有顶点度数都为偶数。
因为这时通过一条边进入一个顶点,必然能从另一条边离开该顶点。
如果当且仅有两个点的度数是奇数的话,则存在欧拉路径,并且起点和终点分别为两个度数为奇数的顶点中的一个。(这时在这两个点之间连一条无向边就存在欧拉回路了)

对于有向图:

当且仅当所有顶点的入度等于出度。
保证进入顶点之后能够离开该顶点,否则进入某个点之后就不能离开这个点。
如果其中仅有一个点出度比入度大1,并且仅有一个点出度比入度小1,则存在欧拉路径,并且前者为起点,后者为终点。(从终点向起点连接一条有向边就存在欧拉回路了)

欧拉回路的求解

可能要花一点时间想一想,但是明白了就觉得很简单了,代码比较简短,根据代码画图跑一遍。(首先要确定存在欧拉回路)可以从起点开始不断寻找路径,可能会走到死胡同。第一次到达死胡同的时候必定是走到了终点(如果原图存在欧拉回路的话就回到了起点,是欧拉路径的话就到达了终点),可以思考一下为什么,这时剩余的边必定能形成欧拉回路,因为此时所有剩余点的度数为偶数(如果是有向图,剩余点的入度和出度相同)。此时回溯到一个可以扩展的点(把回溯过程中的点加入到栈中,记录路径)继续搜索,寻找子欧拉回路(寻找到之后的终点和起点必定相同),把子欧拉回路加入到已找到的欧拉回路中依然是一个欧拉回路。
(欧拉图可以看成是若干个子欧拉图的组成)
下面的代码可以用于寻找欧拉回路和欧拉路径:
时间复杂度为O(E)

iter[MAXE];
memset(iter,0,sizeof(iter));			//记录各顶点当前找到的边,用于优化时间
void dfs(int now){
    for(int &i=iter[now];i<n;i++){
        if(G[now][i]){
            G[now][i] = G[i][now] = 0;//如果是有向图就改成 G[now][i] = 0;
            dfs(i);
        }
    }
    stk.push(now);			//用来存储经过的顶点
}

例题:

  1. [https://www.luogu.org/problemnew/show/P1341] 无向图的欧拉回路 输出顶点字典序最小的欧拉回路,邻接矩阵实现就好了
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 255;
int degree[maxn],have[maxn],bin[maxn],iter[maxn];
int cnt = 0,st = 0;
int G[maxn][maxn];
int n;
stack<int> ans;
int findx(int x){
    if(x!=bin[x]) bin[x] = findx(bin[x]);
    return bin[x];
}
void unity(int a,int b){
    int fa = findx(a);
    int fb = findx(b);
    if(fa!=fb) bin[fa] = fb;
}
void add_edge(int x,int y){
    have[x] = have[y] = 1;
    degree[x]+=1;
    degree[y]+=1;
    G[x][y] = G[y][x] = 1;
}
void input(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        char pat[3];
        scanf("%s",pat);
        int pa=pat[0],pb=pat[1];
        add_edge(pa,pb);
        unity(pa,pb);
    }
}
bool check(){
    int res;
    for(int i='A';i<='z';i++){
        if(have[i]){
            res = bin[i];
            break;
        }
    }
    for(int i='A';i<='z';i++) if(have[i]&&(bin[i]!=res)) return false;
    for(int i='A';i<='z';i++){
        if(have[i]&&(degree[i]&1)){
            cnt++;
            st = st ? min(st,i) : i;
        }
        if(cnt>2) return false;
    }
    return true;
}
void dfs(int cur){
    for(int &i=iter[cur];i<='z';i++){
        if(G[cur][i]){
            G[cur][i] = G[i][cur] = 0;
            dfs(i);
        }
    }
    ans.push(cur);
}
int main(){
    input();
    fill(iter,iter+maxn,'A');
    if(!check()){
        printf("No Solution\n");
        return 0;
    }
    if(st==0){
        for(int i='A';i<='z';i++){
            if(have[i]){
                st = i;
                break;
            }
        }
    }
    dfs(st);
    while(!ans.empty()){
        printf("%c",ans.top());
        ans.pop();
    }
    return 0;
}
  1. POJ1041 [http://poj.org/problem?id=1041] 求边字典序最小的欧拉回路

题目保证给定的图连通,所以就不用判断连通性了

#include
#include
#include
#include
#include
using namespace std;
const int maxn = 2333;
const int maxm = 50;
int G[maxm][maxn];
stack<int> ans;
int vis[maxn];
int iter[maxm],degree[maxm],lst,cnt,home;
void add_edge(int u,int v,int k){
    G[u][k] = v;
    G[v][k] = u;
}
void init(){
    cnt = lst = 0;
    memset(vis,0,sizeof(vis));
	memset(iter,0,sizeof(iter));
	memset(degree,0,sizeof(degree));
	memset(G,0,sizeof(G));
}
bool input(){
	init();
	int pa,pb,k;
	while(scanf("%d %d",&pa,&pb)){
		if(!(pa+pb)) return cnt!=0;
		if(cnt==0) home = pa < pb ? pa : pb;
		scanf("%d",&k);
        add_edge(pa,pb,k);
		lst = max(lst,max(pa,pb));
		cnt = max(k,cnt);
		degree[pa]++;
		degree[pb]++;
	}
}
bool check(){
	for(int i=1;i<=lst;i++) if(degree[i]&1) return false;
	return true;
}
void dfs(int now){
    for(int &i=iter[now];i<=cnt;i++){
        if(!vis[i]&&G[now][i]){
        	int tmp = i;
            vis[i] = 1;
            dfs(G[now][i]);
            ans.push(tmp);
        }
    }
}
void print(){
    while(!ans.empty()){
        ans.size()==1 ? printf("%d",ans.top()) : printf("%d ",ans.top());
        ans.pop();
    }
    printf("\n");
}
int main(){
    while(input()){
        if(!check()){
            printf("Round trip does not exist.\n");
            continue;
        }
        dfs(home);
        print();
    }
	return 0;
}
  1. UVA10129 Play on Words [https://vjudge.net/problem/UVA-10129]判断是否存在欧拉路径
    用并查集判断是否连通,再判断一下出度和入度的关系就行了
#include
#include
#include
using namespace std;
const int maxn = 233;
int T,n;
int have[maxn],bin[maxn],indegree[maxn],outdegree[maxn];
void init(){
	memset(have,0,sizeof(have));
	memset(indegree,0,sizeof(indegree));
	memset(outdegree,0,sizeof(outdegree));
	for(int i=0;i<maxn;i++) bin[i] = i;
}
int findx(int x){
	if(x!=bin[x]) bin[x] = findx(bin[x]);
	return bin[x];
}
void unity(int x,int y){
	int fx = findx(x);
	int fy = findx(y);
	if(fx!=fy) bin[fx] = fy;
}
void input(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		char tmp[1111];
		scanf("%s",tmp);
		int len = strlen(tmp);
		indegree[tmp[0]]++;
		outdegree[tmp[len-1]]++;
		have[tmp[0]] = 1;
		have[tmp[len-1]] = 1;
		unity(tmp[0],tmp[len-1]);
		//printf("%d %d\n",bin[tmp[0]],bin[tmp[len-1]]);
	}
}
bool check(){
	int target;
	for(int i='a';i<='z';i++){
		if(have[i]){
			target = findx(i);
			break;
		}
	}
	int cnta=0,cntb=0;
	for(int i='a';i<='z';i++){
		int del = indegree[i]-outdegree[i];
		if(del==1) cnta++;
		if(del==-1) cntb++;
		if(del<-1||del>1) return false;
		if(have[i]&&findx(i)!=target) return false;
	}
	if(cnta==cntb&&(cnta==0||cnta==1)) return true;
	return false;
}
int main(){
	scanf("%d",&T);
	while(T--){
		init();
		input();
		if(check()) printf("Ordering is possible.\n");
		else printf("The door cannot be opened.\n");
	}
	return 0;
}
  1. POJ2230 Watchcow [http://poj.org/problem?id=2230] 有向图的欧拉回路 dfs可能会爆栈
    建图的时候一条边两个方向都加入一次
    G++交RE,C++交就A了
#include
#include
#include
#include
#include
using namespace std;
typedef pair<int,int> IntPair;			//first表示通往的节点编号,second表示边的使用情况 
const int maxn = 1e4+7;
int n,m;
vector<IntPair> G[maxn];
stack<int> ans;
int iter[maxn];
void init(){
	for(int i=0;i<maxn;i++) G[i].clear();
	memset(iter,0,sizeof(iter));
}
void add_edge(int u,int v){
	G[u].push_back(make_pair(v,0));
	G[v].push_back(make_pair(u,0));
}
void input(){
	for(int i=1;i<=m;i++){
		int pa,pb;
		scanf("%d %d",&pa,&pb);
		add_edge(pa,pb);
	}
}
void euler(int now){
	for(int &i=iter[now];i<G[now].size();i++){
		if(!G[now][i].second){
			G[now][i].second = 1;
			euler(G[now][i].first);
		}
	} 
	ans.push(now);
}
void output(){
	while(!ans.empty()){
		printf("%d\n",ans.top());
		ans.pop();
	}
}
int main(){
	while(scanf("%d %d",&n,&m)!=EOF){
		init();
		input();
		euler(1);
		output();
	}
	return 0;
}
  1. POJ2513 Colored Sticks [http://poj.org/problem?id=2513] 判断欧拉路径,字典树存储节点(map会出问题)并查集判断连通性 和Play on words差不多
#include
#include
#include
using namespace std;
const int maxn = 5e5+7;
struct NODE{
    int id;
    NODE* next[26];
};
int bin[maxn],indegree[maxn];
int node_cnt;
NODE* build(){
    NODE* node = (NODE*)malloc(sizeof(NODE));
    memset(node->next,0,sizeof(node->next));
    node->id = 0;
    return node;
}
int get_id(NODE *root,char* s){
    char* p = s;
    NODE *now = root;
    while(*p){
        int k = *p-'a';
        if(now->next[k]==NULL) now->next[k] = build();
        now = now->next[k];
        p++;
    }
    return now->id ? now->id : now->id=++node_cnt;//如果没有标记 给出序号标记并返回
}
int findx(int x){
    if(x!=bin[x]) bin[x] = findx(bin[x]);
    return bin[x];
}
void unity(int a,int b){
    int fa = findx(a);
    int fb = findx(b);
    if(fa!=fb) bin[fa] = fb;
}
void input_build(NODE* root){
    memset(indegree,0,sizeof(indegree));
    node_cnt = 0;
    for(int i=0;i<maxn;i++) bin[i] = i;
    char sa[20],sb[20];
    while(scanf("%s %s",sa,sb)!=EOF){
        int pa = get_id(root,sa);
        int pb = get_id(root,sb);
        unity(pa,pb);
        indegree[pa]++;
        indegree[pb]++;
    }
}
bool check(){
    for(int i=1;i<node_cnt;i++) if(findx(i)!=findx(i+1)) return false;	//判断连通性
    int num = 0;
    for(int i=0;i<node_cnt;i++) if(indegree[i]&1) num++; //判断节点的度是否合理
    return num<=2;
}
void Trie_clear(NODE* root){
    for(int i=0;i<26;i++) if(root->next[i]) Trie_clear(root->next[i]);
    free(root);
}
int main(){
    NODE* root = build();
    input_build(root);
    if(!check()) printf("Impossible\n");
    else printf("Possible\n");
    Trie_clear(root);
    return 0;
}
  1. POJ2337 Catenyms [http://poj.org/problem?id=2337]无向图的欧拉路径
    找边字典序最小 对边进行排序之后建图 关键要找到欧拉路径的起始点
    反向排序,链式前向星
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1111;
const int maxm = 30;
struct EDGE{
    int to;
    int next;
    string str;
};
stack<string> ans;
int bin[maxm],have[maxm],head[maxm],vis[maxn];
int indegree[maxm],outdegree[maxm];
EDGE edge[maxn];
int T,n,st;
string s[maxn];
bool cmp(string &a,string &b){
    return a>b;
}
int findx(int a){
    if(a!=bin[a]) bin[a] = findx(bin[a]);
    return bin[a];
}
void unity(int a,int b){
    int fa = findx(a);
    int fb = findx(b);
    if(fa!=fb) bin[fa] = fb;
}
void init(){
    st = -1;
    for(int i=0;i<maxm;i++) bin[i] = i;     //并查集前节点
    memset(vis,0,sizeof(vis));              //边访问标记
    memset(head,255,sizeof(head));          //链式前向星头指针
    memset(have,0,sizeof(have));            //点是否使用
    memset(indegree,0,sizeof(indegree));    //各点的入度
    memset(outdegree,0,sizeof(outdegree));  //各点的出度
}
void input(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>s[i];
        int a = s[i][0]-'a';
        int b = s[i][s[i].size()-1]-'a';
        unity(a,b);
        outdegree[a]++;
        indegree[b]++;
        have[a] = have[b] = 1;
    }
}
bool check(){                       //判断图的连通性,判断入度和出度的关系
    int cnta=0,cntb=0,target;
    for(int i=0;i<maxm;i++){
        if(have[i]){
            target = findx(i);
            break;
        }
    }
    for(int i=0;i<maxm;i++) if(have[i]&&(findx(i)!=target)) return false;       //连通性
    for(int i=0;i<maxm;i++){
        int del = indegree[i] - outdegree[i];
        if(del==1) cnta++;
        else if(del==-1) st=i,cntb++;                   //如果有出度大于入度1的点,如果存在欧拉路径,i为起始点
        else if(del<-1||del>1) return false;
    }
    if(cnta>1||cntb>1) return false;                                        //有向图出入度关系
    return true;
}
void add_edge(int u,int v,string& strr,int id){     //建边操作
    edge[id].next = head[u];
    edge[id].str = strr;
    edge[id].to = v;
    head[u] = id;
}
void build(){
    sort(s,s+n,cmp);                                //对边排序,要输出字典序最小的
    for(int i=0;i<n;i++){                       //按顺序建边
        int a = s[i][0]-'a';
        int b = s[i][s[i].size()-1]-'a';
        add_edge(a,b,s[i],i);
    }
    if(st==-1) for(int i=0;i<maxn;i++) if(have[i]){     //如果起点可以是任意的,找字典序最小的起点
        st = i;
        break;
    }
}
void euler(int now){
    for(int i=head[now];i!=-1;i=edge[i].next){
        if(!vis[i]){
            vis[i] = 1;
            euler(edge[i].to);
            ans.push(edge[i].str);
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        init();
        input();
        if(!check()){
            cout<<"***"<<endl;
            continue;;
        }
        build();
        euler(st);
        while(!ans.empty()){
            if(ans.size()==1) cout<<ans.top()<<endl;
            else cout<<ans.top()<<'.';
            ans.pop();
        }
    }
    return 0;
}

你可能感兴趣的:(图论)