哈密顿路
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;
}