哈密尔顿路径问题是一个NP问题,即非多项式问题,其时间复杂度为O(k^n),不可能使用穷举或遍历之类的算法求解。实际上哈密尔顿路径问题是一个NPC问题,即NP完备问题,已经有人证明如果NPC问题可以找到P解,则所有NP问题都有P解。

我这里做的是一个经典问题,即马步问题,描述如下:在国际象棋棋盘上用马这个棋子遍历棋盘,每个格子必须经过且只能经过一次。

该问题稍作变化有三种形式:

1、哈密尔顿链路(Chain):从指定点出发,64步之后完成遍历棋盘,到达任意位置;
2、哈密尔顿回路(Loop):从指定点出发,64步之后完成遍历棋盘,所到达的位置恰好与起始位置相邻1步(马步);
3、哈密尔顿虫路(Worm):从指定点出发,64步完成遍历棋盘,到达指定结束位置(容易证明,指定起始位置和结束位置在8*8的棋盘中一定处于不同颜色的格子中)。

解决该问题没有任何简单办法,只能是尝试,通常采用回溯(有方向性的尝试),但成功率较低,而贪婪算法则采用这样一种思路:尽量先走出路比较少的棋盘格,这样,后面的步骤可选择的余地就大,成功的概率也就大的多。实际上,当后面的步骤回溯时,带来的时间复杂度要小得多,例如回溯到第2步为O(8^62),而回溯到第40步只有O(8^24),显然不是一个数量级的。

程序使用JavaScript编写,绝大多数情况下在极短时间内便能够求得一组解!

基本的注释都有了,就不再解释了: 

  1. <html xmlns="http://www.w3.org/1999/xhtml">  
  2. <head>  
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  4.     <title>Horse traversing problem of Hamilton path(Greedy algorithm) - 哈密尔顿路径之马步问题(贪婪算法)  
  5.     </title>  
  6.     <style type="text/css">  
  7.         body {  
  8.             background-color: #e0ffe0;  
  9.         }  
  10.         p {  
  11.             font-family: 宋体;  
  12.             font-size: 9pt;  
  13.             text-align: center;  
  14.         }  
  15.         table {  
  16.             border-width: 8px;  
  17.             border-color: #404080;  
  18.             border-style: solid;  
  19.             background-color: #ffffff;  
  20.         }  
  21.         td  {  
  22.             font-size: 35px;  
  23.             font-weight: bold;  
  24.             color: #ff6060;  
  25.             text-align: center;  
  26.             vertical-align: middle;  
  27.             width: 70px;  
  28.             height: 70px;  
  29.         }  
  30.         td.b {  
  31.             background-color: #00007f;  
  32.         }  
  33.     </style>  
  34.     <script type="text/javascript">  
  35.         var H = new Array();  
  36.  
  37.         //初始化表示棋盘的二维数组  
  38.         function init() {  
  39.             for (var i = 0; i < 12; i++)  
  40.                 H.push(new Array(12));  
  41.             for (var i = 0; i < H.length; i++)  
  42.                 for (var j = 0; j < H[i].length; j++)  
  43.                     if (i < 2 || i > 9 || j < 2 || j > 9)  
  44.                         H[i][j] = -1 //不允许的位置初始化为-1  
  45.                     else 
  46.                         H[i][j] = 0; //允许的位置为0,以后该值x表示第x步所在的位置,即1到64  
  47.         }  
  48.  
  49.         //这里定义的二维数组表示对应的8种选择的x和y的坐标偏移量  
  50.         var moveOffset = new Array([-2, 1], [-1, 2], [1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1]);  
  51.  
  52.         //贪婪(递归回溯)算法核心思想:  
  53.         //定义:如果点(x, y)下一步可供选择的位置为w,则称该点的度为w  
  54.         //对于任意一节点A,其下一步所有可达节点构成的集合S中,按照度由小到大排列  
  55.         //如果S为空,则回溯到上一步  
  56.         //否则,首先尝试将A节点的下一步移动到度最小的位置  
  57.         //如果该选择导致后面无法移动,则回溯到该位置继续尝试度次小的点  
  58.  
  59.         //贪婪算法的估值函数  
  60.         function wayCosts(x, y, costs) {  
  61.             for (var i = 0; i < 8; i++) {  
  62.                 if (H[x + moveOffset[i][0]][y + moveOffset[i][1]] == 0) {  
  63.                     var w = -1; //计算下一步的度时会统计当前位置(一定可达),故而事先减1  
  64.                     for (var j = 0; j < 8; j++)  
  65.                         if (H[x + moveOffset[i][0] + moveOffset[j][0]][y + moveOffset[i][1] + moveOffset[j][1]] == 0)  
  66.                             w++;  
  67.                     costs.push([w, x + moveOffset[i][0], y + moveOffset[i][1]]);  
  68.                 }  
  69.             } //有一种特殊情况:w=1,但并不意味着该节点是当前位置到下一位置的必经的唯一节点,因为有可能该度为1的节点成为最后一个节点  
  70.             costs.sort(function (a, b) { return a[0] - b[0]; }); //依据度进行非递减序排列,使用匿名函数  
  71.         }  
  72.  
  73.         //哈密尔顿链路函数,递归回溯求解  
  74.         function chain(x, y, step) {  
  75.             var costs = new Array();  
  76.             var flag = false;  
  77.             if (step > 64)  
  78.                 return true 
  79.             else {  
  80.                 wayCosts(x, y, costs);  
  81.                 if (costs.length != 0) {  
  82.                     for (var i = 0; i < costs.length; i++) {  
  83.                         H[costs[i][1]][costs[i][2]] = step;  
  84.                         flag = chain(costs[i][1], costs[i][2], step + 1);  
  85.                         if (!flag)  
  86.                             H[costs[i][1]][costs[i][2]] = 0  
  87.                         else 
  88.                             break;  
  89.                     }  
  90.                 }  
  91.                 return flag;  
  92.             }  
  93.         }  
  94.  
  95.         //哈密尔顿回路思想  
  96.         //与链路不同的是,回路相当于规定了最后一步的位置,即第64步的位置必须与第1步所在位置马步相邻,而虫路则是最后一步的位置已经确定  
  97.         //与第1步(虫路是最后一步)所在位置马步相邻的节点均可作为第64步的节点,而这些节点在搜索过程中,必须至少预留1个  
  98.         var lastCount;  
  99.         var lastX, lastY; //回路求解时需记录起点坐标,虫路求解时需记录终点坐标,用以判断lastCount是否增或减  
  100.  
  101.         //判断两个节点是否为马步相邻  
  102.         function neigh(x1, y1, x2, y2) {  
  103.             return Math.abs(x1 - x2) == 1 && Math.abs(y1 - y2) == 2 || Math.abs(x1 - x2) == 2 && Math.abs(y1 - y2) == 1;  
  104.         }  
  105.  
  106.         //哈密尔顿回路函数  
  107.         function loop(x, y, step) {  
  108.             var costs = new Array();  
  109.             var flag = false;  
  110.             if (step > 64)  
  111.                 return true 
  112.             else {  
  113.                 wayCosts(x, y, costs);  
  114.                 if (costs.length != 0) {  
  115.                     for (var i = 0; i < costs.length; i++) {  
  116.                         H[costs[i][1]][costs[i][2]] = step;  
  117.                         if (neigh(costs[i][1], costs[i][2], lastX, lastY))  
  118.                             lastCount--;  
  119.                         if (lastCount != 0 || step == 64) //当仍然有预留位置,或者虽然没有预留位置,但恰好是求解最后一步  
  120.                             flag = loop(costs[i][1], costs[i][2], step + 1);  
  121.                         if (!flag) {  
  122.                             H[costs[i][1]][costs[i][2]] = 0;  
  123.                             if (neigh(costs[i][1], costs[i][2], lastX, lastY))  
  124.                                 lastCount++;  
  125.                         }  
  126.                         else 
  127.                             break;  
  128.                     }  
  129.                 }  
  130.                 return flag;  
  131.             }  
  132.         }  
  133.  
  134.         //哈密尔顿虫路函数  
  135.         function worm(x, y, step) {  
  136.             var costs = new Array();  
  137.             var flag = false;  
  138.             if (step > 63) //虫路的最后一步已经确定,故而只需求解63步  
  139.                 return true 
  140.             else {  
  141.                 wayCosts(x, y, costs);  
  142.                 if (costs.length != 0) {  
  143.                     for (var i = 0; i < costs.length; i++) {  
  144.                         H[costs[i][1]][costs[i][2]] = step;  
  145.                         if (neigh(costs[i][1], costs[i][2], lastX, lastY))  
  146.                             lastCount--;  
  147.                         if (lastCount != 0 || step == 63) //当仍然有预留位置,或者虽然没有预留位置,但恰好是求解最后一步  
  148.                             flag = worm(costs[i][1], costs[i][2], step + 1);  
  149.                         if (!flag) {  
  150.                             H[costs[i][1]][costs[i][2]] = 0;  
  151.                             if (neigh(costs[i][1], costs[i][2], lastX, lastY))  
  152.                                 lastCount++;  
  153.                         }  
  154.                         else 
  155.                             break;  
  156.                     }  
  157.                 }  
  158.                 return flag;  
  159.             }  
  160.         }  
  161.  
  162.         //===========================================================  
  163.         //设定界面求解相关控件是否可用  
  164.         function runDisabled(flag) {  
  165.             selAlg.disabled = flag;  
  166.             runBtn.disabled = flag;  
  167.             selStartX.disabled = flag;  
  168.             selStartY.disabled = flag;  
  169.             selEndX.disabled = flag;  
  170.             selEndY.disabled = flag;  
  171.         }  
  172.  
  173.         //设定界面演示相关控件是否可用  
  174.         function demoDisabled(flag) {  
  175.             demoBtn.disabled = flag;  
  176.             selDelay.disabled = flag;  
  177.         }  
  178.  
  179.         //求解主函数  
  180.         function run() {  
  181.             runDisabled(true);  
  182.             demoDisabled(true);  
  183.             init();  
  184.             //算法中的二维数组的第1维存放的是列,故而这里进行翻转  
  185.             var startX = parseInt(selStartY.value);  
  186.             var startY = parseInt(selStartX.value);  
  187.             var endX = parseInt(selEndY.value);  
  188.             var endY = parseInt(selEndX.value);  
  189.             H[startX][startY] = 1; //设定第1步的位置  
  190.             if (selAlg.value == "chain")  
  191.                 var func = chain  
  192.             else {  
  193.                 if (selAlg.value == "loop") {  
  194.                     lastX = startX;  
  195.                     lastY = startY;  
  196.                     var func = loop;  
  197.                 }  
  198.                 else {  
  199.                     //哈密尔顿虫路的起点和终点必须在不同颜色的格子中,否则无解  
  200.                     if (((startX + startY + endX + endY) % 2 == 0)) {  
  201.                         alert("哈密尔顿虫路的起点和终点所在棋盘格的颜色必须不同!请重新选择!");  
  202.                         runDisabled(false);  
  203.                         retuen;  
  204.                     }  
  205.                     lastX = endX;  
  206.                     lastY = endY;  
  207.                     H[endX][endY] = 64; //设定第64步的位置  
  208.                     var func = worm;  
  209.                 }  
  210.                 //计算构成回路(虫路)的预留位置数量  
  211.                 lastCount = 0;  
  212.                 for (var i = 0; i < 8; i++)  
  213.                     if (H[lastX + moveOffset[i][0]][lastY + moveOffset[i][1]] == 0)  
  214.                         lastCount++;  
  215.             }  
  216.             alert("这是一个NP问题,尚无完备的求解方法,本程序所使用方法的可行性已经极高!\n如果长时间无法完成求解,则可能需要更长时间,甚至超过宇宙的年龄才能完成,请随时刷新页面取消求解!\n绝大多数情况下在极短时间内便能够求得一组解!")  
  217.             func(startX, startY, 2);  //从第2步开始求解  
  218.             alert("恭喜!求解成功!\n请点击“演示”按钮显示结果!");  
  219.             runDisabled(false);  
  220.             demoDisabled(false);  
  221.         }  
  222.  
  223.         //演示输出函数  
  224.         var demoStep;  
  225.         var intervalID;  
  226.         function draw() {  
  227.             var flag = false;  
  228.             ++demoStep;  
  229.             for (var i = 2; i < 10 && !flag; i++)  
  230.                 for (var j = 2; j < 10 && !flag; j++)  
  231.                     flag = H[i][j] == demoStep;  
  232.             eval("r" + (i - 1 - 2) + "c" + (j - 1 - 2) + ".innerText = \"" + demoStep + "\";"); //退出循环时,i和j的值均大了1,故而需要减去1  
  233.             if (demoStep == 64) {  
  234.                 clearInterval(intervalID); //演示完成后清除定时器  
  235.                 runDisabled(false);  
  236.                 demoDisabled(false);  
  237.             }  
  238.         }  
  239.  
  240.         //演示主函数  
  241.         function demo() {  
  242.             runDisabled(true);  
  243.             demoDisabled(true);  
  244.             //清除所有TD标签中的原有内容  
  245.             var tds = document.getElementsByTagName("TD");  
  246.             for (var i = 0; i < tds.length; i++)  
  247.                 tds[i].innerHTML = "";  
  248.             //延时调用函数绘制图像并标记步骤数字  
  249.             var delay = parseInt(selDelay.value);  
  250.             demoStep = 0;  
  251.             intervalID = setInterval(draw, delay);  
  252.         }  
  253.     </script>  
  254. </head>  
  255. <body>  
  256.     <p>  
  257.         Horse traversing problem of Hamilton path(Greedy algorithm) - 哈密尔顿路径之马步问题(贪婪算法)<br />  
  258.         Mengliao Software Studio(Baiyu) - 梦辽软件工作室(白宇)<br />  
  259.         Copyright 2011, All right reserved. - 版权所有(C) 2011<br />  
  260.         2011.04.07</p>  
  261.     <center>  
  262.         <table cellpadding="0" cellspacing="0">  
  263.             <tr>  
  264.                 <td id="r0c0"></td>  
  265.                 <td class="b" id="r0c1"></td>  
  266.                 <td id="r0c2"></td>  
  267.                 <td class="b" id="r0c3"></td>  
  268.                 <td id="r0c4"></td>  
  269.                 <td class="b" id="r0c5"></td>  
  270.                 <td id="r0c6"></td>  
  271.                 <td class="b" id="r0c7"></td>  
  272.             </tr>  
  273.             <tr>  
  274.                 <td class="b" id="r1c0"></td>  
  275.                 <td id="r1c1"></td>  
  276.                 <td class="b" id="r1c2"></td>  
  277.                 <td id="r1c3"></td>  
  278.                 <td class="b" id="r1c4"></td>  
  279.                 <td id="r1c5"></td>  
  280.                 <td class="b" id="r1c6"></td>  
  281.                 <td id="r1c7"></td>  
  282.             </tr>  
  283.             <tr>  
  284.                 <td id="r2c0"></td>  
  285.                 <td class="b" id="r2c1"></td>  
  286.                 <td id="r2c2"></td>  
  287.                 <td class="b" id="r2c3"></td>  
  288.                 <td id="r2c4"></td>  
  289.                 <td class="b" id="r2c5"></td>  
  290.                 <td id="r2c6"></td>  
  291.                 <td class="b" id="r2c7"></td>  
  292.             </tr>  
  293.             <tr>  
  294.                 <td class="b" id="r3c0"></td>  
  295.                 <td id="r3c1"></td>  
  296.                 <td class="b" id="r3c2"></td>  
  297.                 <td id="r3c3"></td>  
  298.                 <td class="b" id="r3c4"></td>  
  299.                 <td id="r3c5"></td>  
  300.                 <td class="b" id="r3c6"></td>  
  301.                 <td id="r3c7"></td>  
  302.             </tr>  
  303.             <tr>  
  304.                 <td id="r4c0"></td>  
  305.                 <td class="b" id="r4c1"></td>  
  306.                 <td id="r4c2"></td>  
  307.                 <td class="b" id="r4c3"></td>  
  308.                 <td id="r4c4"></td>  
  309.                 <td class="b" id="r4c5"></td>  
  310.                 <td id="r4c6"></td>  
  311.                 <td class="b" id="r4c7"></td>  
  312.             </tr>  
  313.             <tr>  
  314.                 <td class="b" id="r5c0"></td>  
  315.                 <td id="r5c1"></td>  
  316.                 <td class="b" id="r5c2"></td>  
  317.                 <td id="r5c3"></td>  
  318.                 <td class="b" id="r5c4"></td>  
  319.                 <td id="r5c5"></td>  
  320.                 <td class="b" id="r5c6"></td>  
  321.                 <td id="r5c7"></td>  
  322.             </tr>  
  323.             <tr>  
  324.                 <td id="r6c0"></td>  
  325.                 <td class="b" id="r6c1"></td>  
  326.                 <td id="r6c2"></td>  
  327.                 <td class="b" id="r6c3"></td>  
  328.                 <td id="r6c4"></td>  
  329.                 <td class="b" id="r6c5"></td>  
  330.                 <td id="r6c6"></td>  
  331.                 <td class="b" id="r6c7"></td>  
  332.             </tr>  
  333.             <tr>  
  334.                 <td class="b" id="r7c0"></td>  
  335.                 <td id="r7c1"></td>  
  336.                 <td class="b" id="r7c2"></td>  
  337.                 <td id="r7c3"></td>  
  338.                 <td class="b" id="r7c4"></td>  
  339.                 <td id="r7c5"></td>  
  340.                 <td class="b" id="r7c6"></td>  
  341.                 <td id="r7c7"></td>  
  342.             </tr>  
  343.         </table>  
  344.         <p>  
  345.             算法  
  346.             <select id="selAlg">  
  347.                 <option value="chain" selected="selected">哈密尔顿链路 (Chain)</option>  
  348.                 <option value="loop">哈密尔顿回路 (Loop)</option>  
  349.                 <option value="worm">哈密尔顿虫路 (Worm)</option>  
  350.             </select>&nbsp;&nbsp;&nbsp;  
  351.             <input type="button" id="runBtn" value="尝试求解..." style="width: 80px; height: 25px" onclick="run();" />&nbsp;&nbsp;&nbsp;  
  352.             <input type="button" id="demoBtn" value="演示..." style="width: 80px; height: 25px" disabled="disabled" onclick="demo();" />&nbsp;&nbsp;&nbsp;演示速度  
  353.             <select id="selDelay" disabled="disabled">  
  354.                 <option value="100">0.1s/步</option>  
  355.                 <option value="200">0.2s/步</option>  
  356.                 <option value="300" selected="selected">0.3s/步</option>  
  357.                 <option value="500">0.5s/步</option>  
  358.                 <option value="700">0.7s/步</option>  
  359.                 <option value="1000">1s/步</option>  
  360.                 <option value="1500">1.5s/步</option>  
  361.                 <option value="2000">2s/步</option>  
  362.             </select>  
  363.             <br /><br />起点X坐标  
  364.             <select id="selStartX">  
  365.                 <option value="2" selected="selected">第1列</option>  
  366.                 <option value="3">第2列</option>  
  367.                 <option value="4">第3列</option>  
  368.                 <option value="5">第4列</option>  
  369.                 <option value="6">第5列</option>  
  370.                 <option value="7">第6列</option>  
  371.                 <option value="8">第7列</option>  
  372.                 <option value="9">第8列</option>  
  373.             </select>&nbsp;&nbsp;&nbsp;起点Y坐标  
  374.             <select id="selStartY">  
  375.                 <option value="2" selected="selected">第1行</option>  
  376.                 <option value="3">第2行</option>  
  377.                 <option value="4">第3行</option>  
  378.                 <option value="5">第4行</option>  
  379.                 <option value="6">第5行</option>  
  380.                 <option value="7">第6行</option>  
  381.                 <option value="8">第7行</option>  
  382.                 <option value="9">第8行</option>  
  383.             </select>&nbsp;&nbsp;&nbsp;终点X坐标  
  384.             <select id="selEndX">  
  385.                 <option value="2" selected="selected">第1列</option>  
  386.                 <option value="3">第2列</option>  
  387.                 <option value="4">第3列</option>  
  388.                 <option value="5">第4列</option>  
  389.                 <option value="6">第5列</option>  
  390.                 <option value="7">第6列</option>  
  391.                 <option value="8">第7列</option>  
  392.                 <option value="9">第8列</option>  
  393.             </select>&nbsp;&nbsp;&nbsp;终点Y坐标  
  394.             <select id="selEndY">  
  395.                 <option value="2" selected="selected">第1行</option>  
  396.                 <option value="3">第2行</option>  
  397.                 <option value="4">第3行</option>  
  398.                 <option value="5">第4行</option>  
  399.                 <option value="6">第5行</option>  
  400.                 <option value="7">第6行</option>  
  401.                 <option value="8">第7行</option>  
  402.                 <option value="9">第8行</option>  
  403.             </select>  
  404.         </p>  
  405.     </center>  
  406. </body>  
  407. </html> 

将上面的代码直接保存成网页文件,在本地打开就可以了。