图论--哈密顿路与欧拉路

哈密顿路

1、哈密顿路定义
设G=<V,E>为一图(无向图或有向图)
哈密顿路:是指不重复的走过图G中所有的点,则构成哈密顿通路。
不重复:节点且只经过一次。
哈密顿回路:是指不重复的走过图G中所有的点,并且回到起点。
哈密顿图:若G中存在哈密顿回路,则称它是哈密顿图

在这里插入图片描述

2、哈密顿图条件
注意:目前没有找到哈密顿图的简单的充要条件
哈密顿图的必要条件: 若G=(V,E) 是一个哈密顿图,则对于V的每一个非空子集S,均有W(G-S) ≤|S|。其中|S|是S中的顶点数,W(G-S)表示图G擦去属于S中的顶点后,剩下子图的连通分支的个数。

(1)该图去掉s个顶点之后的分支数一定小于等于去掉的顶点数s,若不满足一定不是哈密顿图。
(2)有割点的图一定不是哈密顿图

哈密顿图的充分条件: 设G=(V,E)是一个无向简单图,|V|=n. n≥3. 若对于任意的两个顶点u,v∊V,d(u)+d(v) ≥n,那么, G是哈密尔顿图。其中d(u),d(v)分别代表顶点u,v的度数。
定理1:设G是n(n≥3)阶无向简单图,如果G中任何一对不相邻的顶点度数之和都大于等于n,则G是哈密顿图。
推论:设G是n(n>=3)阶无向简单图,若G的最小度>=n/2,则G是哈密顿图
定理2:在n(n≥2)阶有向图D=<V,E>中,如果所有有向边均用无向边代替,所得无向图中含生成子图Kn,则有向图中存在哈密顿图。
推论:n(n≥3)阶有向完全图为哈密顿图

1.美国图论数学家奥勒在1960年给出了一个图是哈密尔顿图的充分条件:对于顶点个数大于2的图,如果图中任意两点度的和大于或等于顶点总数,那这个图一定是哈密顿图。但不满足不一定就不是哈密顿图
2.若图的最小度不小于顶点数的一半,则图是哈密顿图;
3.若图中每一对不相邻的顶点的度数之和不小于顶点数,则图是哈密顿图。

3、哈密顿图判定
(1)若能通过观察找出图G中的一条哈密顿回路,则G当然是哈密顿图。
使用DFS就可以求出图中的哈密尔顿环
(2)若一个无向图G满足上述定理1中的条件,一个有向图D满足上述定理2的推论的条件,则G、D都是哈密顿图。

例题:邮递员送信

邮递员在送信时,为了节省路途,自己规定:每次总是从n个村子中选择其中一个合适的村子出发,途中每个村子仅且经过一次,送完所有的信。已知各个村子的道路连通情况。输出所有符合要求的路线。如果没有输出“no road”。
输入:第一行:整数n,村子的个数。接下来是一个n*n的0、1矩阵,表示n个村子的连同情况,如:a[i,j]=1 ,表示第i和第j个村子之间有路可走,如果a[i,j]=0,表示他们之间无路可走。
输出:按序号从小到大输出所有可行的线路
输入:
7
0 1 0 1 1 0 0
1 0 1 0 1 0 0
0 1 0 0 0 0 1
1 0 0 0 0 0 0
1 1 0 0 0 1 0
0 0 0 0 1 0 1
0 0 1 0 0 1 0
输出:
2 3 7 6 5 1 4
3 7 6 5 2 1 4
4 1 2 3 7 6 5
4 1 2 5 6 7 3
4 1 5 2 3 7 6
4 1 5 6 7 3 2
5 6 7 3 2 1 4
6 7 3 2 5 1 4

【问题分析】
不重复的走完所有点,依然可以用图的深度优先遍历,但需要记录并输出路径

【参考程序】*/ 
#include<iostream>
#include<cstring>
using namespace std;
int a[101][101],n,found;
int b[101],d[101];
void print() //输出路径
{
    found=1;
    for(int i=1;i<=n;i++)
     cout<<d[i]<<" ";	
	
}   
void dfs(int k,int num) //k表示当前点,num表示k为第几个点
{
  if (num==n) 
   {
       print();
       cout<<endl;
return ;
    }
     for(int i=1;i<=n;i++)
     if ((b[i]==0)&&(a[k][i]==1))
     {         b[i]=1;
         d[num+1]=i;
	     dfs(i,num+1);  
		 b[i]=0;
     } 
}
int main()
{   found=0;//标记是否找到哈密尔顿路
   cin>>n;
   memset(b,0,sizeof(b));
   for(int i=1;i<=n;i++)
       for(int j=1;j<=n;j++)   cin>>a[i][j];
           
   for(int i=1;i<=n;i++) //枚举出发点
   {        b[i]=1; //标记i点已加入当前路径
       d[1]=i; //将i点记入路径
       dfs(i,1);
       b[i]=0;
   }  	
    if (found==0) cout<<"no road";   
	return 0;
}
 

欧拉路

1、定义:如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那个路径叫做欧拉回路。
旁白:不重复的一次性遍历图的所有边

 奇点:图中跟这个点相连的边的数目为奇数个的点
 定理1:存在欧拉路的条件:无向图是连通的,有且只有2个奇点。
 定理2:存在欧拉回路的条件:无向图是连通的,有0个奇点。
 定理3:对于有向图而言,存在欧拉回路的条件是,所有点的出度和入度相等;或者有且仅有两个点的出度和入度不相等,则该两点一个为起点(出度比入度多1),一个为终点(入度比出度多1),则存在欧拉路。

2、、求欧拉路的算法
(1)求各个点的度,并判断是否是欧拉回路
(2)从奇点(或者任意点)开始DFS,并记录路径
(3)避免重复走边,遍历过的边删除
(4)时间复杂度O(n+m)n,m为图的顶点和边的数目

代码实现

/*该程序只能找到一个欧拉路,多个欧拉路不能顺利找到*/ 
#include<iostream>
#include<cstring>
#define maxn 101 
using namespace std;
int a[maxn][maxn];
int du[maxn];//记录各个点的度 
int cir[maxn];//记录路径 
int n,m,cirsum=0,start;
void find_dfs(int i)
{
	for(int j=1;j<=n;j++)
	{
		if(a[i][j]==1)
		{
			a[i][j]=a[j][i]=0;//删除这条边,避免重复 
			find_dfs(j);
		}
	}
	cir[++cirsum]=i;//记录路径 
}
int main()
{
	int x,y;
	memset(a,0,sizeof(a));
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		a[x][y]=a[y][x]=1;
		du[x]++;
		du[y]++;
	}
	start=1;//找不到奇点,是欧拉回路,就从任意一点开始dfs 
	for(int i=1;i<=n;i++)
	{
		if(du[i]%2==1)//有奇点则找到的是欧拉路,否则是欧拉回路,从任意一个点开始dfs就可以 
		 start=i;
	 } 
	find_dfs(start);
	for(int i=1;i<=cirsum;i++)
	{
		cout<<cir[i]<<"->";
	} 
	return 0;
 } 

例:[欧拉路径]
【题解】
本题需要判断 + 找出有向图的欧拉路径。
由于本题保证“将有向边视为无向边后图连通”,所以判定时不用判断连通性。
还有一点要注意的是本题需要按照字典序输出。
这一点如何解决呢?
法一:
直接使用数组存邻接矩阵,枚举点 x 出边时,直接枚举编号从 1 到 n 的点 y,再判断 x,y 之间是否有未访问边,这样就解决了字典序的问题。
但是这样的做法时间复杂度为O(n2),显然会超时。

void dfs(int now)
{
    for(int i=1;i<=n;i++)
    {
        if(G[u][i]>0)
        {
            G[u][i]--;
            dfs(i);
        }
    }
    st.push(now);
}

法二:
既然邻接矩阵不行,那我们就用时间复杂度更优的邻接表,将 now 的所有出边排序即可。

#include <bits/stdc++.h>
using namespace std;
const int MAX=100010;
int n,m,u,v,del[MAX];
int du[MAX][2];//记录入度和出度 
stack <int> st;
vector <int> G[MAX];
void dfs(int now)
{
	for(int i=del[now];i<G[now].size();i=del[now])
	{ 
		del[now]=i+1;
		dfs(G[now][i]);
	}
	st.push(now);
}
int main()
{
	scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),G[u].push_back(v),du[u][1]++,du[v][0]++;  
    for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
    int S=1,cnt[2]={0,0}; //记录
    bool flag=1; //flag=1表示,所有的节点的入度都等于出度,
    for(int i=1;i<=n;i++)
	{
        if(du[i][1]!=du[i][0]) flag=0;
        if(du[i][1]-du[i][0]==1/*出度比入度多1*/) cnt[1]++,S=i;
        if(du[i][0]-du[i][1]==1/*入度比出度多1*/) cnt[0]++;
    }
    if((!flag)&&!(cnt[0]==cnt[1]&&cnt[0]==1)) return !printf("No");
	//不满足欧拉回路的判定条件,也不满足欧拉路径的判定条件,直接输出"No" 
    dfs(S);
    while(!st.empty()) printf("%d ",st.top()),st.pop();
    return 0; 
}

练习题目

1、马修栅栏
2、无序字母对
3、词链

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信奥教练Andy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值