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

查看原文:点击打开链接


定义


欧拉问题分为欧拉路径以及欧拉回路。

     
  • 欧拉路径,指在图中找得到一条路径,使得该路径对图的每一条边恰好访问一次。

  •  
  • 欧拉回路,指在图中找得到一个圈,使得该圈恰好经过每一条边一次。


由上可见,路径与回路的区别仅在于起点与终点是否是同一个点。

无向图判定定理


首先保证图G是一个连通的图。

     
  • 无向图G存在欧拉回路的充要条件是,图G里所有顶点的度为偶数。

  •  
  • 无向图G存在欧拉路径的充要条件是,图G里有且仅有两个奇度数的顶点,其分别为路径的起点及终点。

  •  
  • 若图G里有多余两个的奇度数顶点,则不存在欧拉路径。


有向图判定定理


判定有向图的欧拉问题时,首先得判定有向图的基图(即其无向图)是连通的。

     
  • 有向图G存在欧拉回路的充要条件是,图G里每一个顶点的入度与出度都相等。

  •  
  • 有向图G存在欧拉路径的充要条件是,存在两个顶点作为起点与终点,其中起点的入度比出度少1,终点的入度比出度多1。


算法


第一个算法就是上边讲的,去统计每个顶点的度数,然后判断基图是否是连通的。


第二个算法由《数据结构与算法分析》给出,其复杂度仅为O(V+E),称其为套圈法,待以后分析。

例子:单词接龙


拉姆刚开始学习英文单词,对单词排序很感兴趣。如果给拉姆一组单词,他能够迅速确定是否可以将这些单词排列在一个列表中,使得该列表中任何单词的首字母与前一单词的尾字母相同。你能编写一个程序来帮助拉姆进行判断吗?


输入描述:
输入包含多组测试数据。对于每组测试数据,第一行为一个正整数n,代表有n个单词。然后有n个字符串,代表n个单词。保证:2<=n<=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

 


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


>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
#include
#include
#include


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 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"<else
cout<<"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 
*****/


```

你可能感兴趣的:(数据结构相关,图论算法)