無向圖經過所有的節點一次且僅一次:哈密瓜路徑
無向圖經過所有的邊一次且僅一次:歐拉路徑
帶權哈密頓路徑就是旅行商問題。
參考博客:https://blog.csdn.net/zhouzi2018/article/details/81278942
過程:
1:任意找兩個相鄰的節點S和T,在其基礎上擴展出一條儘量長的沒有重複結點的路徑.即如果S與結點v相鄰,而且v不在路徑S -> T上,則可以把該路徑變成v -> S -> T,然後v成爲新的S.從S和T分別向兩頭擴展,直到無法繼續擴展爲止,即所有與S或T相鄰的節點都在路徑S -> T上.
2:若S與T相鄰,則路徑S -> T形成了一個迴路.
3:若S與T不相鄰,可以構造出來一個迴路.設路徑S -> T上有k+2個節點,依次爲S, v1, v2, ..., vk, T.可以證明存在節點vi(i屬於[1, k]),滿足vi與T相鄰,且vi+1與S相鄰.找到這個節點vi,把原路徑變成S -> vi -> T -> vi+1 ,即形成了一個迴路.
4:到此爲止,已經構造出來了一個沒有重複節點的的迴路,如果其長度爲N,則哈密頓迴路就找到了.如果迴路的長度小於N,由於整個圖是連通的,所以在該回路上,一定存在一點與迴路之外的點相鄰.那麼從該點處把迴路斷開,就變回了一條路徑,同時還可以將與之相鄰的點加入路徑.再按照步驟1的方法儘量擴展路徑,則一定有新的節點被加進來.接着回到路徑2.
證明:
跟據鴿巢定理,既然與S和T相鄰的點都在路徑上,它們分佈的範圍只有v1,v2---,vk這k個點,k<=N-2,跟據哈密頓迴路的第一個判斷條件,d(S)+d(T)>=N,那麼v1,v2,---vk這k個點中一定有至少2個點同時與S和T相連,根據鴿巢定理,肯定存在一個與S相鄰的點vi和一個與T相鄰的點vj,滿足j=i+1
僞代碼:
設s爲哈密頓迴路的起始點,t爲哈密頓迴路中終點s之前的點.ans[]爲最終的哈密頓迴路.倒置的意思指的是將數組對應的區間中數字的排列順序反向.
1:初始化,令s = 1,t爲s的任意一個鄰接點.
2:如果ans[]中元素的個數小於n,則從t開始向外擴展,如果有可擴展點v,放入ans[]的尾部,並且t=v,並繼續擴展,如無法擴展進入步驟3.
3:將當前得到的ans[]倒置,s和t互換,從t開始向外擴展,如果有可擴展點v,放入ans[]尾部,並且t=v,並繼續擴展.如無法擴展進入步驟4.
4:如果當前s和t相鄰,進入步驟5.否則,遍歷ans[],尋找點ans[i],使得ans[i]與t相連並且ans[i +1]與s相連,將從ans[i + 1]到t部分的ans[]倒置,t=ans[i +1],進如步驟5.
5:如果當前ans[]中元素的個數等於n,算法結束,ans[]中保存了哈密頓迴路(可看情況是否加入點s).否則,如果s與t連通,但是ans[]中的元素的個數小於n,則遍歷ans[],尋找點ans[i],使得ans[i]與ans[]外的一點(j)相連,則令s=ans[i - 1],t = j,將ans[]中s到ans[i - 1]部分的ans[]倒置,將ans[]中的ans[i]到t的部分倒置,將點j加入到ans[]的尾部,轉步驟2.
#include<cstring>
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=410;
int ans[maxn];
int visit[maxn];// in case of reoccurrence
int friendt[maxn][maxn];
int n,m;
int s,t;
int num;
void rever(int s,int t){
while(s<t){
int tmp=ans[s];
ans[s]=ans[t];
ans[t]=tmp;
s++;t--;
}
}
void hamilton()
{
s=1;
for(int i=1;i<=n;i++){
if(friendt[s][i]){t=i;break;}
}
ans[0]=s;ans[1]=t;
num=2;
//cout<<s<<" "<<t<<endl;
memset(visit,0,sizeof(visit));
visit[s]=1;visit[t]=1;
int v;
while(1){
while(1){
v=0;
for(int i=1;i<=n;i++){
if(friendt[t][i]&&!visit[i]){v=i;break;}
}
if(v){
ans[num++]=v;t = v;visit[v]=1;
}
else break;
}
//cout<<t<<endl;
rever(0,num-1);int tmp=s;s=t;t=tmp;
while(1){
v=0;
for(int i=1;i<=n;i++){
if(friendt[t][i]&&!visit[i]){v=i;break;}
}
if(v){
ans[num++]=v;t = v;visit[v]=1;
}
else break;
}
if(!friendt[s][t]){
for(int i=1;i<num-2;i++){
if(friendt[ans[i]][t]&&friendt[ans[i+1]][s]){
rever(i+1,num-1);t=ans[i+1];break;
}
}
}
if(num==n)return;
int ind,j;
for(int i=1;i<=n;i++){if(!visit[i]){j=i;break;}}
for(int k=1;k<num-2;k++){
if(friendt[ans[k]][j]){ind=k;break;}
}
s=ans[ind-1];t=j;rever(0,ind-1);rever(ind,num-1);
ans[num++]=j;visit[j]=1;
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(cin>>n>>m&&(n||m)){
n = 2*n;
//cout<<n<<endl;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{if(j==i)friendt[i][j]=0;else friendt[i][j]=1;}
for(int i=0;i<m;i++){
int a,b;
scanf("%d%d",&a,&b);
friendt[a][b]=0;
friendt[b][a]=0;
}
num=0;
hamilton();
for(int i=0;i<n;i++)printf("%d ",ans[i]);
cout<<endl;
}
return 0;
}