哈密頓回路算法詳解


【轉】哈密頓回路


原文鏈接:http://www.cnblogs.com/Ash-ly/p/5452580.html

 

概念:

  哈密頓圖:圖G的一個回路,若它通過圖的每一個節點一次,且僅一次,就是哈密頓回路.存在哈密頓回路的圖就是哈密頓圖.哈密頓圖就是從一點出發,經過所有的必須且只能一次,最終回到起點的路徑.圖中有的邊可以不經過,但是不會有邊被經過兩次.

  與歐拉圖的區別:歐拉圖討論的實際上是圖上關於邊的可行便利問題,而哈密頓圖的要求與點有關.

判定:

一:Dirac定理(充分條件)

  設一個無向圖中有N個頂點,若所有頂點的度數大於等於N/2,則哈密頓回路一定存在.(N/2指的是⌈N/2⌉,向上取整)

二:基本的必要條件

  設圖G=<V, E>是哈密頓圖,則對於v的任意一個非空子集S,若以|S|表示S中元素的數目,G-S表示G中刪除了S中的點以及這些點所關聯的邊后得到的子圖,則W(G-S)<=|S|成立.其中W(G-S)是G-S中聯通分支數.

三:競賽圖(哈密頓通路)

  N(N>=2)階競賽圖一點存在哈密頓通路.

算法:

一:在Dirac定理的前提下構造哈密頓回路

過程:

  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 -> S,即形成了一個回路.

  4:到此為止,已經構造出來了一個沒有重復節點的的回路,如果其長度為N,則哈密頓回路就找到了.如果回路的長度小於N,由於整個圖是連通的,所以在該回路上,一定存在一點與回路之外的點相鄰.那么從該點處把回路斷開,就變回了一條路徑,同時還可以將與之相鄰的點加入路徑.再按照步驟1的方法盡量擴展路徑,則一定有新的節點被加進來.接着回到路徑2.

證明:

  可利用鴿巢原理證明.

偽代碼:

  設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.

時間復雜度:

  如果說每次到步驟5算一輪的話,那么由於每一輪當中至少有一個節點被加入到路徑S -> T中,所以總的輪數肯定不超過n輪,所以時間復雜度為O(n^2).空間上由於邊數非常多,所以采用鄰接矩陣來存儲比較適合.

代碼:

 
 1 const int maxN = 100;
 2 inline void reverse(int arv[maxN + 7], int s, int t){//將數組anv從下標s到t的部分的順序反向
 3     int temp;
 4     while(s  < t){
 5         temp = arv[s];
 6         arv[s] = arv[t];
 7         arv[t] = temp;
 8         s++;
 9         t--;
10     }
11 }
12 
13 void Hamilton(int ans[maxN + 7], bool map[maxN + 7][maxN + 7], int n){
14     int s = 1, t;//初始化取s為1號點
15     int ansi = 2;
16     int i, j;
17     int w;
18     int temp;
19     bool visit[maxN + 7] = {false};
20     for(i = 1; i <= n; i++) if(map[s][i]) break;
21     t = i;//取任意鄰接與s的點為t
22     visit[s] = visit[t] = true;
23     ans[0] = s;
24     ans[1] = t;
25     while(true){
26         while(true){//從t向外擴展
27             for(i = 1; i <= n; i++){
28                 if(map[t][i] && !visit[i]){
29                     ans[ansi++] = i;
30                     visit[i] = true;
31                     t = i;
32                     break;
33                 }
34             }
35             if(i > n) break;
36         }
37         w = ansi - 1;//將當前得到的序列倒置,s和t互換,從t繼續擴展,相當於在原來的序列上從s向外擴展
38         i = 0;
39         reverse(ans, i, w);
40         temp = s;
41         s = t;
42         t = temp;
43         while(true){//從新的t繼續向外擴展,相當於在原來的序列上從s向外擴展
44             for(i = 1; i <= n; i++){
45                 if(map[t][i] && !visit[i]){
46                     ans[ansi++] = i;
47                     visit[i] = true;
48                     t = i;
49                     break;
50                 }
51             }
52             if(i > n) break;    
53         }
54         if(!map[s][t]){//如果s和t不相鄰,進行調整
55             for(i = 1; i < ansi - 2; i++)//取序列中的一點i,使得ans[i]與t相連,並且ans[i+1]與s相連
56                 if(map[ans[i]][t] && map[s][ans[i + 1]])break;
57             w = ansi - 1;
58             i++;
59             t = ans[i];
60             reverse(ans, i, w);//將從ans[i +1]到t部分的ans[]倒置
61         }//此時s和t相連
62         if(ansi == n) return;//如果當前序列包含n個元素,算法結束
63         for(j = 1; j <= n; j++){//當前序列中元素的個數小於n,尋找點ans[i],使得ans[i]與ans[]外的一個點相連
64             if(visit[j]) continue;
65             for(i = 1; i < ansi - 2; i++)if(map[ans[i]][j])break;
66                 if(map[ans[i]][j]) break;
67         }
68         s = ans[i - 1];
69         t = j;//將新找到的點j賦給t
70         reverse(ans, 0, i - 1);//將ans[]中s到ans[i-1]的部分倒置
71         reverse(ans, i, ansi - 1);//將ans[]中ans[i]到t的部分倒置
72         ans[ansi++] = j;//將點j加入到ans[]尾部
73         visit[j] = true;
74     }
75 }
 

 

二:N(N>=2)階競賽圖構造哈密頓通路

N階競賽圖:含有N個頂點的有向圖,且每對頂點之間都有一條邊.對於N階競賽圖一定存在哈密頓通路.

數學歸納法證明競賽圖在n >= 2時必存在哈密頓路:

(1)n = 2時結論顯然成立;

(2)假設n = k時,結論也成立,哈密頓路為V1, V2, V3, ..., Vk;

  設當n = k+1時,第k + 1個節點為V(k+1),考慮到V(k+1)與Vi(1<=i<=k)的連通情況,可以分為以下兩種情況.

    1:Vk與V(k+1)兩點之間的弧為<Vk, V(k+1)>,則可構造哈密頓路徑V1, V2,…, Vk, V(k+1).

    2:Vk與V(k+1)兩點之間的弧為<V(k+1),Vk>,則從后往前尋找第一個出現的Vi(i=k-1,i>=1,--i),滿足Vi與V(k+1)之間的弧為<Vi,V(k+1)>,則構造哈密頓路徑V1, V2, …, Vi, V(k+1), V(i+1), …, V(k).若沒找到滿足條件的Vi,則說明對於所有的Vi(1<=i<=k)到V(k+1)的弧為<V(k+1),V(i)>,則構造哈密頓路徑V(k+1), V1, V2, …, Vk.

證畢.

競賽圖構造哈密頓路時的算法同以上證明過程.

 

用圖來說明:

假設此時已經存在路徑V1 -> V2 -> V3 -> V4,這四個點與V5的連通情況有16種,給定由0/1組成的四個數,第i個數為0代表存在弧<V5,Vi>,反之為1,表示存在弧<Vi,V5>

 

 

 

 

sign[]={0, 0, 0, 0}.

很顯然屬於第二種情況,從后往前尋找不到1,即且不存在弧<Vi, V5>.

則構造哈密頓路:V5 -> V1 -> V2 -> V3 -> V4.

 

 

 

sign[]={0, 0, 0, 1}.

屬於第一種情況,最后一個數字為1,即代表存在弧<Vi, V5>且i=4(最后一個點)

則構造哈密頓路: V1 -> V2 -> V3 -> V4 -> V5.

 

 

 

 

sign[]={0, 0, 1, 0}.

屬於第二種情況,從后往前找到1出現的第一個位置為3.

構造哈密頓路: V1 -> V2 -> V3 -> V5 -> V4.

 

 

 

 

sign[]={0, 0, 1, 1}.

屬於第一種情況,最后一個數字為1,即代表存在弧<Vi, V5>且i=4(最后一個點)

則構造哈密頓路: V1 -> V2 -> V3 -> V4 -> V5.

 

 

 

 

sign[]={0, 1, 0, 0}.

屬於第二種情況,從后往前找到1出現的第一個位置為2.

構造哈密頓路: V1 -> V2 -> V5 -> V3-> V4.

 

 

 

 

sign[]={0, 1, 0, 1}.

屬於第一種情況,最后一個數字為1,即代表存在弧<Vi, V5>且i=4(最后一個點)

則構造哈密頓路:V1 -> V2 -> V3 -> V4 -> V5.(就不舉末尾為1的栗子了~~)

 

 

 

 

sign[]={1, 0, 1, 0}.

屬於第二種情況,從后往前找到1出現的第一個位置為3.

構造哈密頓路: V1 -> V2 -> V3 -> V5-> V4.

 

 

 

 

sign[]={1, 1, 1, 0}.

屬於第二種情況,從后往前找到1出現的第一個位置為3.

構造哈密頓路: V1 -> V2 -> V3 -> V5-> V4.

 

 

 

 

(還是舉一個吧~~~)

sign[]={1, 1, 1, 1}.

同樣最后一位為1,代表存在<Vi, V5>且i=4(最后一位)

則構造哈密頓路:V1 -> V2 -> V3 -> V4 -> V5.以上是當N=4時(N+1=5),用圖來闡述算法的過程.

注意從后往前找不是找這個點編號之前的點,即不是按照編號來的,而是按照當前哈密頓序列從后往前找的.舉個栗子:

4

2 1

1 3

3 2

4 1

4 2

4 3

第一步ans={1}

第二步ans={2,1}

第三步sign={0, 1}(map[3][2] = 0,map[3][1] = 1,當前序列為2,1) ,而不是{1, 0}(1,2),因為存在弧<V1, V3>和<V3, V2>.這里需要注意下.

代碼:

 

 
 1 #include <iostream>
 2 #include <cmath>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <cstdlib>
 6 #include <algorithm>
 7 #include <queue>
 8 #include <stack>
 9 #include <vector>
10 
11 using namespace std;
12 typedef long long LL;
13 const int maxN = 200;
14 
15 //The arv[] length is len, insert key befor arv[index] 
16 inline void Insert(int arv[], int &len, int index, int key){ 
17     if(index > len) index = len;
18     len++;
19     for(int i = len - 1; i >= 0; --i){
20         if(i != index && i)arv[i] = arv[i - 1];
21         else{arv[i] = key; return;}
22     }
23 }
24 
25 void Hamilton(int ans[maxN + 7], int map[maxN + 7][maxN + 7], int n){
26     int ansi = 1;
27     ans[ansi++] = 1;
28     for(int i = 2; i <= n; i++){//第一種情況,直接把當前點添加到序列末尾
29         if(map[i][ans[ansi - 1]] == 1)
30             ans[ansi++] = i;
31         else{
32             int flag = 0;
33             for(int j = ansi - 2; j > 0; --j){//在當前序列中,從后往前找到第一個滿足條件的點j,使得存在<Vj,Vi>且<Vi, Vj+1>.
34                 if(map[i][ans[j]] == 1){//找到后把該點插入到序列的第j + 1個點前.
35                     flag = 1;
36                     Insert(ans, ansi, j + 1, i);
37                     break;
38                 }
39             }
40             if(!flag)Insert(ans, ansi, 1, i);//否則說明所有點都鄰接自點i,則把該點直接插入到序列首端.
41         }
42     }
43 }
44 
45 int main()
46 {
47     //freopen("input.txt", "r", stdin);
48     int t;
49     scanf("%d", &t);
50     while(t--){
51         int N;
52         scanf("%d", &N);
53         int M = N * (N - 1) / 2;
54         int map[maxN + 7][maxN + 7] = {0};
55         for(int i = 0; i < M; i++){
56             int u, v;
57             scanf("%d%d", &u, &v);
58             //map[i][j]為1說明j < i,且存在弧<Vi, Vj>,因為插入時只考慮該點之前的所有點的位置,與之后的點沒有關系.所以只注重該點與其之前的點的連通情況.
59             if(u < v)map[v][u] = 1;
60         }
61         int ans[maxN + 7] = {0};
62         Hamilton(ans, map, N);
63         for(int i = 1; i <= N; i++)
64             printf(i == 1 ? "%d":" %d", ans[i]);
65         printf("\n");
66     }
67     return 0;
68 }
 

 代碼2:

 
 1 void Hamilton(int ans[maxN + 7], int map[maxN + 7][maxN + 7], int n){
 2     int nxt[maxN + 7];
 3     memset(nxt, -1, sizeof(nxt));
 4     int head = 1;
 5     for(int i = 2; i <= n; i++){
 6         if(map[i][head]){
 7             nxt[i] = head;
 8             head = i;
 9         }else{
10             int pre = head, pos = nxt[head];
11             while(pos != -1 && !map[i][pos]){
12                 pre = pos;
13                 pos = nxt[pre];
14             }
15             nxt[pre] = i;
16             nxt[i] = pos;
17         }
18     }
19     int cnt = 0;
20     for(int i = head; i != -1; i = nxt[i])
21         ans[++cnt] = i;
22 }
 

代碼三:

 
 1 void Hamitton(bool reach[N + 7][N + 7], int n)  
 2 {    
 3     vector <int> ans; 
 4     ans.push_back(1);  
 5     for(int i=2;i <= n;i++)  
 6     {  
 7         bool cont = false;  
 8         for(int j=0;j<(int)ans.size()-1;j++)  
 9             if(reach[ ans[j] ][i] && reach[i][ ans[j+1] ])  
10             {  
11                 ans.insert(ans.begin()+j+1,i);  
12                 cont = true;  
13                 break;  
14             }  
15         if(cont)  
16             continue;  
17         if(reach[ ans.back() ][i])  
18             ans.push_back(i);  
19         else  
20             ans.insert(ans.begin(),i);  
21     } 
22     for(int i=0;i<n;i++)  
23                    printf("%d%c",ans[i],i==n-1?'\n':' ');   
24 } 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM