貪婪算法求解哈密爾頓路徑問題

哈密爾頓路徑問題是一個NP問題,即非多項式問題,其時間複雜度爲O(k^n),不可能使用窮舉或遍歷之類的算法求解。實際上哈密爾頓路徑問題是一個NPC問題,即NP完備問題,已經有人證實若是NPC問題能夠找到P解,則全部NP問題都有P解。javascript

我這裏作的是一個經典問題,即馬步問題,描述以下:在國際象棋棋盤上用馬這個棋子遍歷棋盤,每一個格子必須通過且只能通過一次。css

該問題稍做變化有三種形式:html

一、哈密爾頓鏈路(Chain):從指定點出發,64步以後完成遍歷棋盤,到達任意位置;
二、哈密爾頓迴路(Loop):從指定點出發,64步以後完成遍歷棋盤,所到達的位置剛好與起始位置相鄰1步(馬步);
三、哈密爾頓蟲路(Worm):從指定點出發,64步完成遍歷棋盤,到達指定結束位置(容易證實,指定起始位置和結束位置在8*8的棋盤中必定處於不一樣顏色的格子中)。java

解決該問題沒有任何簡單辦法,只能是嘗試,一般採用回溯(有方向性的嘗試),但成功率較低,而貪婪算法則採用這樣一種思路:儘可能先走出路比較少的棋盤格,這樣,後面的步驟可選擇的餘地就大,成功的機率也就大的多。實際上,當後面的步驟回溯時,帶來的時間複雜度要小得多,例如回溯到第2步爲O(8^62),而回溯到第40步只有O(8^24),顯然不是一個數量級的。算法

程序使用JavaScript編寫,絕大多數狀況下在極短期內便可以求得一組解!數組

基本的註釋都有了,就再也不解釋了: ide

  
  
  
  
  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> 

將上面的代碼直接保存成網頁文件,在本地打開就能夠了。函數

相關文章
相關標籤/搜索