图论欧拉路径问题(单词接龙)

查看原文:点击打开链接


<h2>定义</h2>
欧拉问题分为欧拉路径以及欧拉回路。
<ul>
  <li>欧拉路径,指在图中找得到一条路径,使得该路径对图的每一条边恰好访问一次。</li>
  <li>欧拉回路,指在图中找得到一个圈,使得该圈恰好经过每一条边一次。</li>
</ul>
由上可见,路径与回路的区别仅在于起点与终点是否是同一个点。
<h2>无向图判定定理</h2>
首先保证图G是一个连通的图。
<ul>
  <li>无向图G存在欧拉回路的充要条件是,图G里所有顶点的度为偶数。</li>
  <li>无向图G存在欧拉路径的充要条件是,图G里有且仅有两个奇度数的顶点,其分别为路径的起点及终点。</li>
  <li>若图G里有多余两个的奇度数顶点,则不存在欧拉路径。</li>
</ul>
<h2>有向图判定定理</h2>
判定有向图的欧拉问题时,首先得判定有向图的基图(即其无向图)是连通的。
<ul>
  <li>有向图G存在欧拉回路的充要条件是,图G里每一个顶点的入度与出度都相等。</li>
  <li>有向图G存在欧拉路径的充要条件是,存在两个顶点作为起点与终点,其中起点的入度比出度少1,终点的入度比出度多1。</li>
</ul>
<h2>算法</h2>
第一个算法就是上边讲的,去统计每个顶点的度数,然后判断基图是否是连通的。


第二个算法由《数据结构与算法分析》给出,其复杂度仅为O(V+E),称其为套圈法,待以后分析。
<h2>例子:单词接龙</h2>
<blockquote>拉姆刚开始学习英文单词,对单词排序很感兴趣。如果给拉姆一组单词,他能够迅速确定是否可以将这些单词排列在一个列表中,使得该列表中任何单词的首字母与前一单词的尾字母相同。你能编写一个程序来帮助拉姆进行判断吗?


输入描述:
输入包含多组测试数据。对于每组测试数据,第一行为一个正整数n,代表有n个单词。然后有n个字符串,代表n个单词。保证:2&lt;=n&lt;=200,每个单词长度大于1且小于等于10,且所有单词都是由小写字母组成。


输出描述:
对于每组数据,输出"Yes"或"No"


输入例子1:
3
abc
cdefg
ghijkl


输出:
Yes


输入例子2:
4
abc
cdef
fghijk
xyz


输出:
No


输入例子3:
3
aba
cdc
efe


输出:
No


输入例子4:
4
abc
cde
cfg
ghc


输出:
YES</blockquote>
&nbsp;


这道题在以前我使用递归的方式做过,不过复杂度比较高,去检测了把每一个单词当成首单词后的每一种情况。具体见博文:


>http://www.wyblog.cn/2016/05/21/%E9%A2%98%E7%9B%AE2-%E5%8D%95%E8%AF%8D%E6%8E%A5%E9%BE%99/


实际上这道题就是一个有向图的欧拉路径问题。我们把每个单词看做一条边,单词首字母为起点,单词尾字母为终点,那么这就是一个求有向图的欧拉路径问题。需要做的工作就是两部分,一是判断图是否是连通的,而是判断是否含有欧拉路径或者欧拉回路。代码如下:
```


#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>


using namespace std;
#define MAX_VERTEX_NUM 27


int UnDirecCounter; //统计无向图的顶点个数,用于判定无向图是否是连通的
int DirecCounter; //统计有向图顶点个数 
int Visit[MAX_VERTEX_NUM]; //标记顶点是否被访问过 
 
void Init_Globalpara(void)
{
UnDirecCounter=0;
memset(Visit,0,sizeof(Visit));
}


typedef struct EdgeNode
{
int adjVertex;
int isDirecEdge; //标记此边是否为有向边 
EdgeNode *nextEdgeNode;
}EdgeNode;


typedef struct VerNode
{
int data;
int inDegree;
int outDegree;
EdgeNode *firstedge;
}VerNode;


typedef struct Graph
{
VerNode verNode[MAX_VERTEX_NUM];
int vertex_num,edge_num;
}Graph;


void CreateDAG(Graph &G,int n) 
{
int k;
char word[10];
char i,j;
G.edge_num=n;
EdgeNode *p;

for(k=1;k<=26;k++) //初始化头结点
{
G.verNode[k].data=k;
G.verNode[k].inDegree=0;
G.verNode[k].outDegree=0;
G.verNode[k].firstedge=NULL;
}

for(k=1;k<=n;k++) //同时创建有向图及无向图 
{
cin>>word;
i=word[0]; //首字母 
j=word[strlen(word)-1]; //尾字母


if(!Visit[i-'a'+1]) //计算无向图顶点个数 
{
Visit[i-'a'+1]=1;
UnDirecCounter++; 
}
if(!Visit[j-'a'+1]) 
{
Visit[j-'a'+1]=1;
UnDirecCounter++; 
}

G.verNode[j-'a'+1].inDegree++; //更新入度及出度 
G.verNode[i-'a'+1].outDegree++;

p=new EdgeNode;
p->isDirecEdge=1; //有向边,用来标志有向图 
p->adjVertex=(j-'a'+1);
p->nextEdgeNode=G.verNode[i-'a'+1].firstedge;
G.verNode[i-'a'+1].firstedge=p; 

p=new EdgeNode;
p->isDirecEdge=0; //无向边 
p->adjVertex=(i-'a'+1);
p->nextEdgeNode=G.verNode[j-'a'+1].firstedge;
G.verNode[j-'a'+1].firstedge=p;
}
}


int isWordList(Graph &G)
{
//首先判断有向图的基图是否为弱连通的 
int isConnected=0;
VerNode V;
EdgeNode *w;
queue<VerNode> Q;
int start=1;
int tempCount=0;
while(!Visit[start]) start++; //寻找图的一个起点 
memset(Visit,0,sizeof(Visit)); //清空Visit,以便DFS时记录
Q.push(G.verNode[start]);
Visit[start]=1;
while(!Q.empty())
{
V=Q.front();Q.pop();
tempCount++;
w=V.firstedge;
while(w)
{
if(!Visit[w->adjVertex])
{
Q.push(G.verNode[w->adjVertex]);
Visit[G.verNode[w->adjVertex].data]=1;
}
w=w->nextEdgeNode;
}
}
if(tempCount==UnDirecCounter) //遍历到的顶点数等于图的顶点数,则为连通的 
isConnected=1;

//然后寻找欧拉路径或者欧拉回路 
int hasEuler=1;
int startNum=0; //统计起点及终点个数 
int endNum=0;
for(int v=1;v<=26;v++)
{
if((G.verNode[v].outDegree-G.verNode[v].inDegree)==1)
startNum++;
if((G.verNode[v].inDegree-G.verNode[v].outDegree)==1)
endNum++;
if(abs(G.verNode[v].inDegree-G.verNode[v].outDegree)>1)
{
hasEuler=0;
break;
}
}
int isEulerPath=(startNum==1 && endNum==1); //包含欧拉路径
int isEulerCircuit=(startNum==0 && endNum==0); //包含欧拉回路 
if((!isEulerPath)&&(!isEulerCircuit))
hasEuler=0;
 
if(hasEuler&&isConnected)
return 1;
else
return 0; 
}


int main()
{
int n;
cin>>n;
Graph G;
Init_Globalpara();
CreateDAG(G,n);
if(isWordList(G))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl; 
}


/*****
输入例子1:
3
abc
cdefg
ghijkl
输出:
Yes


输入例子2:
4
abc
cdef
fghijk
xyz
输出:
No


输入例子3:
3
aba
cdc
efe
输出:
No


输入例子4: 
4
abc
cde
cfg
ghc
输出:
YES 
*****/


```

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值