概念:
哈密顿图:图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 }