本文鏈接: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 }