哈密爾頓路徑問題是一個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
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>Horse traversing problem of Hamilton path(Greedy algorithm) - 哈密爾頓路徑之馬步問題(貪婪算法)
- </title>
- <style type="text/css">
- body {
- background-color: #e0ffe0;
- }
- p {
- font-family: 宋體;
- font-size: 9pt;
- text-align: center;
- }
- table {
- border-width: 8px;
- border-color: #404080;
- border-style: solid;
- background-color: #ffffff;
- }
- td {
- font-size: 35px;
- font-weight: bold;
- color: #ff6060;
- text-align: center;
- vertical-align: middle;
- width: 70px;
- height: 70px;
- }
- td.b {
- background-color: #00007f;
- }
- </style>
- <script type="text/javascript">
- var H = new Array();
- //初始化表示棋盤的二維數組
- function init() {
- for (var i = 0; i < 12; i++)
- H.push(new Array(12));
- for (var i = 0; i < H.length; i++)
- for (var j = 0; j < H[i].length; j++)
- if (i < 2 || i > 9 || j < 2 || j > 9)
- H[i][j] = -1 //不容許的位置初始化爲-1
- else
- H[i][j] = 0; //容許的位置爲0,之後該值x表示第x步所在的位置,即1到64
- }
- //這裏定義的二維數組表示對應的8種選擇的x和y的座標偏移量
- var moveOffset = new Array([-2, 1], [-1, 2], [1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1]);
- //貪婪(遞歸回溯)算法核心思想:
- //定義:若是點(x, y)下一步可供選擇的位置爲w,則稱該點的度爲w
- //對於任意一節點A,其下一步全部可達節點構成的集合S中,按照度由小到大排列
- //若是S爲空,則回溯到上一步
- //不然,首先嚐試將A節點的下一步移動到度最小的位置
- //若是該選擇致使後面沒法移動,則回溯到該位置繼續嘗試度次小的點
- //貪婪算法的估值函數
- function wayCosts(x, y, costs) {
- for (var i = 0; i < 8; i++) {
- if (H[x + moveOffset[i][0]][y + moveOffset[i][1]] == 0) {
- var w = -1; //計算下一步的度時會統計當前位置(必定可達),故而事先減1
- for (var j = 0; j < 8; j++)
- if (H[x + moveOffset[i][0] + moveOffset[j][0]][y + moveOffset[i][1] + moveOffset[j][1]] == 0)
- w++;
- costs.push([w, x + moveOffset[i][0], y + moveOffset[i][1]]);
- }
- } //有一種特殊狀況:w=1,但並不意味着該節點是當前位置到下一位置的必經的惟一節點,由於有可能該度爲1的節點成爲最後一個節點
- costs.sort(function (a, b) { return a[0] - b[0]; }); //依據度進行非遞減序排列,使用匿名函數
- }
- //哈密爾頓鏈路函數,遞歸回溯求解
- function chain(x, y, step) {
- var costs = new Array();
- var flag = false;
- if (step > 64)
- return true
- else {
- wayCosts(x, y, costs);
- if (costs.length != 0) {
- for (var i = 0; i < costs.length; i++) {
- H[costs[i][1]][costs[i][2]] = step;
- flag = chain(costs[i][1], costs[i][2], step + 1);
- if (!flag)
- H[costs[i][1]][costs[i][2]] = 0
- else
- break;
- }
- }
- return flag;
- }
- }
- //哈密爾頓迴路思想
- //與鏈路不一樣的是,迴路至關於規定了最後一步的位置,即第64步的位置必須與第1步所在位置馬步相鄰,而蟲路則是最後一步的位置已經肯定
- //與第1步(蟲路是最後一步)所在位置馬步相鄰的節點都可做爲第64步的節點,而這些節點在搜索過程當中,必須至少預留1個
- var lastCount;
- var lastX, lastY; //迴路求解時需記錄起點座標,蟲路求解時需記錄終點座標,用以判斷lastCount是否增或減
- //判斷兩個節點是否爲馬步相鄰
- function neigh(x1, y1, x2, y2) {
- return Math.abs(x1 - x2) == 1 && Math.abs(y1 - y2) == 2 || Math.abs(x1 - x2) == 2 && Math.abs(y1 - y2) == 1;
- }
- //哈密爾頓迴路函數
- function loop(x, y, step) {
- var costs = new Array();
- var flag = false;
- if (step > 64)
- return true
- else {
- wayCosts(x, y, costs);
- if (costs.length != 0) {
- for (var i = 0; i < costs.length; i++) {
- H[costs[i][1]][costs[i][2]] = step;
- if (neigh(costs[i][1], costs[i][2], lastX, lastY))
- lastCount--;
- if (lastCount != 0 || step == 64) //當仍然有預留位置,或者雖然沒有預留位置,但剛好是求解最後一步
- flag = loop(costs[i][1], costs[i][2], step + 1);
- if (!flag) {
- H[costs[i][1]][costs[i][2]] = 0;
- if (neigh(costs[i][1], costs[i][2], lastX, lastY))
- lastCount++;
- }
- else
- break;
- }
- }
- return flag;
- }
- }
- //哈密爾頓蟲路函數
- function worm(x, y, step) {
- var costs = new Array();
- var flag = false;
- if (step > 63) //蟲路的最後一步已經肯定,故而只需求解63步
- return true
- else {
- wayCosts(x, y, costs);
- if (costs.length != 0) {
- for (var i = 0; i < costs.length; i++) {
- H[costs[i][1]][costs[i][2]] = step;
- if (neigh(costs[i][1], costs[i][2], lastX, lastY))
- lastCount--;
- if (lastCount != 0 || step == 63) //當仍然有預留位置,或者雖然沒有預留位置,但剛好是求解最後一步
- flag = worm(costs[i][1], costs[i][2], step + 1);
- if (!flag) {
- H[costs[i][1]][costs[i][2]] = 0;
- if (neigh(costs[i][1], costs[i][2], lastX, lastY))
- lastCount++;
- }
- else
- break;
- }
- }
- return flag;
- }
- }
- //===========================================================
- //設定界面求解相關控件是否可用
- function runDisabled(flag) {
- selAlg.disabled = flag;
- runBtn.disabled = flag;
- selStartX.disabled = flag;
- selStartY.disabled = flag;
- selEndX.disabled = flag;
- selEndY.disabled = flag;
- }
- //設定界面演示相關控件是否可用
- function demoDisabled(flag) {
- demoBtn.disabled = flag;
- selDelay.disabled = flag;
- }
- //求解主函數
- function run() {
- runDisabled(true);
- demoDisabled(true);
- init();
- //算法中的二維數組的第1維存放的是列,故而這裏進行翻轉
- var startX = parseInt(selStartY.value);
- var startY = parseInt(selStartX.value);
- var endX = parseInt(selEndY.value);
- var endY = parseInt(selEndX.value);
- H[startX][startY] = 1; //設定第1步的位置
- if (selAlg.value == "chain")
- var func = chain
- else {
- if (selAlg.value == "loop") {
- lastX = startX;
- lastY = startY;
- var func = loop;
- }
- else {
- //哈密爾頓蟲路的起點和終點必須在不一樣顏色的格子中,不然無解
- if (((startX + startY + endX + endY) % 2 == 0)) {
- alert("哈密爾頓蟲路的起點和終點所在棋盤格的顏色必須不一樣!請從新選擇!");
- runDisabled(false);
- retuen;
- }
- lastX = endX;
- lastY = endY;
- H[endX][endY] = 64; //設定第64步的位置
- var func = worm;
- }
- //計算構成迴路(蟲路)的預留位置數量
- lastCount = 0;
- for (var i = 0; i < 8; i++)
- if (H[lastX + moveOffset[i][0]][lastY + moveOffset[i][1]] == 0)
- lastCount++;
- }
- alert("這是一個NP問題,尚無完備的求解方法,本程序所使用方法的可行性已經極高!\n若是長時間沒法完成求解,則可能須要更長時間,甚至超過宇宙的年齡才能完成,請隨時刷新頁面取消求解!\n絕大多數狀況下在極短期內便可以求得一組解!")
- func(startX, startY, 2); //從第2步開始求解
- alert("恭喜!求解成功!\n請點擊「演示」按鈕顯示結果!");
- runDisabled(false);
- demoDisabled(false);
- }
- //演示輸出函數
- var demoStep;
- var intervalID;
- function draw() {
- var flag = false;
- ++demoStep;
- for (var i = 2; i < 10 && !flag; i++)
- for (var j = 2; j < 10 && !flag; j++)
- flag = H[i][j] == demoStep;
- eval("r" + (i - 1 - 2) + "c" + (j - 1 - 2) + ".innerText = \"" + demoStep + "\";"); //退出循環時,i和j的值均大了1,故而須要減去1
- if (demoStep == 64) {
- clearInterval(intervalID); //演示完成後清除定時器
- runDisabled(false);
- demoDisabled(false);
- }
- }
- //演示主函數
- function demo() {
- runDisabled(true);
- demoDisabled(true);
- //清除全部TD標籤中的原有內容
- var tds = document.getElementsByTagName("TD");
- for (var i = 0; i < tds.length; i++)
- tds[i].innerHTML = "";
- //延時調用函數繪製圖像並標記步驟數字
- var delay = parseInt(selDelay.value);
- demoStep = 0;
- intervalID = setInterval(draw, delay);
- }
- </script>
- </head>
- <body>
- <p>
- Horse traversing problem of Hamilton path(Greedy algorithm) - 哈密爾頓路徑之馬步問題(貪婪算法)<br />
- Mengliao Software Studio(Baiyu) - 夢遼軟件工做室(白宇)<br />
- Copyright 2011, All right reserved. - 版權全部(C) 2011<br />
- 2011.04.07</p>
- <center>
- <table cellpadding="0" cellspacing="0">
- <tr>
- <td id="r0c0"></td>
- <td class="b" id="r0c1"></td>
- <td id="r0c2"></td>
- <td class="b" id="r0c3"></td>
- <td id="r0c4"></td>
- <td class="b" id="r0c5"></td>
- <td id="r0c6"></td>
- <td class="b" id="r0c7"></td>
- </tr>
- <tr>
- <td class="b" id="r1c0"></td>
- <td id="r1c1"></td>
- <td class="b" id="r1c2"></td>
- <td id="r1c3"></td>
- <td class="b" id="r1c4"></td>
- <td id="r1c5"></td>
- <td class="b" id="r1c6"></td>
- <td id="r1c7"></td>
- </tr>
- <tr>
- <td id="r2c0"></td>
- <td class="b" id="r2c1"></td>
- <td id="r2c2"></td>
- <td class="b" id="r2c3"></td>
- <td id="r2c4"></td>
- <td class="b" id="r2c5"></td>
- <td id="r2c6"></td>
- <td class="b" id="r2c7"></td>
- </tr>
- <tr>
- <td class="b" id="r3c0"></td>
- <td id="r3c1"></td>
- <td class="b" id="r3c2"></td>
- <td id="r3c3"></td>
- <td class="b" id="r3c4"></td>
- <td id="r3c5"></td>
- <td class="b" id="r3c6"></td>
- <td id="r3c7"></td>
- </tr>
- <tr>
- <td id="r4c0"></td>
- <td class="b" id="r4c1"></td>
- <td id="r4c2"></td>
- <td class="b" id="r4c3"></td>
- <td id="r4c4"></td>
- <td class="b" id="r4c5"></td>
- <td id="r4c6"></td>
- <td class="b" id="r4c7"></td>
- </tr>
- <tr>
- <td class="b" id="r5c0"></td>
- <td id="r5c1"></td>
- <td class="b" id="r5c2"></td>
- <td id="r5c3"></td>
- <td class="b" id="r5c4"></td>
- <td id="r5c5"></td>
- <td class="b" id="r5c6"></td>
- <td id="r5c7"></td>
- </tr>
- <tr>
- <td id="r6c0"></td>
- <td class="b" id="r6c1"></td>
- <td id="r6c2"></td>
- <td class="b" id="r6c3"></td>
- <td id="r6c4"></td>
- <td class="b" id="r6c5"></td>
- <td id="r6c6"></td>
- <td class="b" id="r6c7"></td>
- </tr>
- <tr>
- <td class="b" id="r7c0"></td>
- <td id="r7c1"></td>
- <td class="b" id="r7c2"></td>
- <td id="r7c3"></td>
- <td class="b" id="r7c4"></td>
- <td id="r7c5"></td>
- <td class="b" id="r7c6"></td>
- <td id="r7c7"></td>
- </tr>
- </table>
- <p>
- 算法
- <select id="selAlg">
- <option value="chain" selected="selected">哈密爾頓鏈路 (Chain)</option>
- <option value="loop">哈密爾頓迴路 (Loop)</option>
- <option value="worm">哈密爾頓蟲路 (Worm)</option>
- </select>
- <input type="button" id="runBtn" value="嘗試求解..." style="width: 80px; height: 25px" onclick="run();" />
- <input type="button" id="demoBtn" value="演示..." style="width: 80px; height: 25px" disabled="disabled" onclick="demo();" /> 演示速度
- <select id="selDelay" disabled="disabled">
- <option value="100">0.1s/步</option>
- <option value="200">0.2s/步</option>
- <option value="300" selected="selected">0.3s/步</option>
- <option value="500">0.5s/步</option>
- <option value="700">0.7s/步</option>
- <option value="1000">1s/步</option>
- <option value="1500">1.5s/步</option>
- <option value="2000">2s/步</option>
- </select>
- <br /><br />起點X座標
- <select id="selStartX">
- <option value="2" selected="selected">第1列</option>
- <option value="3">第2列</option>
- <option value="4">第3列</option>
- <option value="5">第4列</option>
- <option value="6">第5列</option>
- <option value="7">第6列</option>
- <option value="8">第7列</option>
- <option value="9">第8列</option>
- </select> 起點Y座標
- <select id="selStartY">
- <option value="2" selected="selected">第1行</option>
- <option value="3">第2行</option>
- <option value="4">第3行</option>
- <option value="5">第4行</option>
- <option value="6">第5行</option>
- <option value="7">第6行</option>
- <option value="8">第7行</option>
- <option value="9">第8行</option>
- </select> 終點X座標
- <select id="selEndX">
- <option value="2" selected="selected">第1列</option>
- <option value="3">第2列</option>
- <option value="4">第3列</option>
- <option value="5">第4列</option>
- <option value="6">第5列</option>
- <option value="7">第6列</option>
- <option value="8">第7列</option>
- <option value="9">第8列</option>
- </select> 終點Y座標
- <select id="selEndY">
- <option value="2" selected="selected">第1行</option>
- <option value="3">第2行</option>
- <option value="4">第3行</option>
- <option value="5">第4行</option>
- <option value="6">第5行</option>
- <option value="7">第6行</option>
- <option value="8">第7行</option>
- <option value="9">第8行</option>
- </select>
- </p>
- </center>
- </body>
- </html>
將上面的代碼直接保存成網頁文件,在本地打開就能夠了。函數