漢諾塔非遞迴演算法

weixin_33763244發表於2017-11-08

這兩天講《Web程式設計》,用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>Hanoi Tower (Recursive algorithm) - 漢諾塔(遞迴演算法)</title> 
  5. <style type="text/css"> 
  6. i {  
  7.     color: #0000ff;  
  8. }  
  9. P {  
  10.     text-align: center;  
  11. }  
  12. </style> 
  13. <script type="text/javascript"> 
  14. var step=0;  
  15. function MoveOnePlate(n, loca1, loca2)  
  16. {  
  17.     document.write("第" + ++step + "步:移動第<i>" + n + "</i>個盤子,從" + loca1 + "到" + loca2 + "<br />");  
  18. }  
  19.  
  20. function MovePlates(n, s, m, d)  
  21. {  
  22.     if(n==1)  
  23.       MoveOnePlate(n, s, d);  
  24.     else  
  25.     {  
  26.         MovePlates(n-1, s, d, m);  
  27.         MoveOnePlate(n, s, d);  
  28.         MovePlates(n-1, m, s, d);  
  29.     }  
  30. }  
  31. </script> 
  32. </head> 
  33.  
  34. <body> 
  35. <p>Hanoi Tower (Recursive algorithm) - 漢諾塔(遞迴演算法)<br />Mengliao Software Studio(Baiyu) - 夢遼軟體工作室(白宇)<br /> 
  36. Copyright 2011, All right reserved. - 版權所有(C) 2011<br />2011.03.31</p> 
  37. <script type="text/javascript"> 
  38.     n=parseInt(prompt("請輸入盤子的數量:", 3), 10);  
  39.     if (isNaN(n) || n<1 || n>16)  
  40.     {  
  41.         alert("請輸入介於1到16的自然數!");  
  42.         location.reload();  
  43.     }  
  44.     else  
  45.     {  
  46.         document.write("共" + n + "個盤子,需移動" + (Math.pow(2, n)-1) + "步:<br /><br />");  
  47.         MovePlates(n, "<i>源點</i>", "<i>臨時</i>", "<i>目標</i>");  
  48.     }  
  49. </script> 
  50. </body> 
  51. </html> 

這個遞迴演算法就不詳細說了,核心程式碼只有5、6行。
下面是非遞迴演算法: 


  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>Hanoi Tower (Non-recursive algorithm) - 漢諾塔(非遞迴演算法)</title> 
  5. <style type="text/css"> 
  6. i {  
  7.     color: #ff0000;  
  8. }  
  9. p {  
  10.     text-align: center;  
  11. }  
  12. </style> 
  13. <!--  
  14. 漢諾塔非遞迴演算法:  
  15. (1)、若問題規模n為偶數,按順時針方向依次擺放s, m, d為環,若n為奇數,則為s, d, m;  
  16. (2)、將盤子由小到大逐個編號並放置在s上,即最小的盤子編號為1,放在最上面;  
  17. (3)、按順時針方向把編號為1的盤子從當前的位置移動到下一位置;  
  18. (4)、把另外兩個位置(即除去1號盤子所在的位置)中非空的那個上的一個圓盤移到空的位置上,如果另外兩個位置都非空,則移動編號較小的那一個;  
  19. (5)、重複進行(3)和(4),直到移動的次數等於2^n-1。  
  20. --> 
  21. <script type="text/javascript"> 
  22. var step=0;  
  23. function MoveOnePlate(n, loca1, loca2)  
  24. {  
  25.     document.write("第" + ++step + "步:移動第<i>" + n + "</i>個盤子,從" + loca1 + "到" + loca2 + "<br />");  
  26. }  
  27.  
  28. function MovePlates(n, s, m, d)  
  29. {  
  30.     //定義位置  
  31.     var loop=new Array(3);  
  32.     loop[0]=new Array(n);  
  33.     loop[1]=new Array(n);  
  34.     loop[2]=new Array(n);  
  35.     //定義位置描述字串陣列(n為偶數的情況)  
  36.     var loca=new Array(s, m, d);  
  37.     if (n%2!=0) //n為奇數的情況  
  38.     {  
  39.         loca[1]=d;  
  40.         loca[2]=m;  
  41.     }  
  42.     //初始化源位置上的盤子  
  43.     for(var i=0; i<n; i++)  
  44.         loop[0][i]=n-i;  
  45.     //記錄各個位置上盤子的數量  
  46.     var loopLen=new Array(n, 0, 0);  
  47.     var count=Math.pow(2, n)-1; //移動次數,即迴圈退出條件  
  48.     var firstPlate=0; //1號盤子的位置  
  49.     do  
  50.     {  
  51.         //將1號盤子順時針移動到後1個位置  
  52.         MoveOnePlate(1, loca[firstPlate], loca[(firstPlate+1)%3]); //顯示移動過程  
  53.         loop[(firstPlate+1)%3][loopLen[(firstPlate+1)%3]]=1; //移動  
  54.         loopLen[firstPlate]--; //修改1號盤子舊位置上盤子的數量  
  55.         firstPlate=(firstPlate+1)%3; //修改1號盤子的位置  
  56.         loopLen[firstPlate]++; //修改1號盤子新位置上盤子的數量  
  57.         count--; //記錄移動次數  
  58.         //移動另外的兩個位置上的盤子  
  59.         if(count!=0) //避免最後一次移動後仍然移動而導致錯誤  
  60.         {  
  61.             //確定另外兩個位置如何移動  
  62.             if (loopLen[(firstPlate+1)%3]==0 || loopLen[(firstPlate+2)%3]!=0 &&  
  63.                 loop[(firstPlate+2)%3][loopLen[(firstPlate+2)%3]-1] < loop[(firstPlate+1)%3][loopLen[(firstPlate+1)%3]-1] )  
  64.             { //1號盤子的後第1個位置為空,或者無空位置且1號盤子後第2個位置編號較小,此時將1號盤子後第2個位置的盤子移動到1號盤子後第1個位置上  
  65.                 MoveOnePlate(loop[(firstPlate+2)%3][loopLen[(firstPlate+2)%3]-1], loca[(firstPlate+2)%3], loca[(firstPlate+1)%3]); //顯示移動過程  
  66.                 loop[(firstPlate+1)%3][loopLen[(firstPlate+1)%3]]=loop[(firstPlate+2)%3][loopLen[(firstPlate+2)%3]-1]; //移動  
  67.                 loopLen[(firstPlate+2)%3]--; //修改該盤子舊位置上盤子的數量  
  68.                 loopLen[(firstPlate+1)%3]++; //修改該盤子新位置上盤子的數量  
  69.             }  
  70.             else  
  71.             { //1號盤子的後第2個位置為空,或者無空位置且1號盤子後第1個位置編號較小,此時將1號盤子後第1個位置的盤子移動到1號盤子後第2個位置上  
  72.                 MoveOnePlate(loop[(firstPlate+1)%3][loopLen[(firstPlate+1)%3]-1], loca[(firstPlate+1)%3], loca[(firstPlate+2)%3]); //顯示移動過程  
  73.                 loop[(firstPlate+2)%3][loopLen[(firstPlate+2)%3]]=loop[(firstPlate+1)%3][loopLen[(firstPlate+1)%3]-1]; //移動  
  74.                 loopLen[(firstPlate+1)%3]--; //修改該盤子舊位置上盤子的數量  
  75.                 loopLen[(firstPlate+2)%3]++; //修改該盤子新位置上盤子的數量  
  76.             }  
  77.             count--; //記錄移動次數  
  78.         }  
  79.     } while(count!=0)  
  80. }  
  81. </script> 
  82. </head> 
  83.  
  84. <body> 
  85. <p>Hanoi Tower (Non-recursive algorithm) - 漢諾塔(非遞迴演算法)<br />Mengliao Software Studio(Baiyu) - 夢遼軟體工作室(白宇)<br /> 
  86. Copyright 2011, All right reserved. - 版權所有(C) 2011<br />2011.03.31</p> 
  87. <script type="text/javascript"> 
  88.     n=parseInt(prompt("請輸入盤子的數量:", 3), 10);  
  89.     if (isNaN(n) || n<1 || n>16)  
  90.     {  
  91.         alert("請輸入介於1到16的自然數!");  
  92.         location.reload();  
  93.     }  
  94.     else  
  95.     {  
  96.         document.write("共" + n + "個盤子,需移動" + (Math.pow(2, n)-1) + "步:<br /><br />");  
  97.         MovePlates(n, "<i>源點</i>", "<i>臨時</i>", "<i>目標</i>");  
  98.     }  
  99. </script> 
  100. </body> 
  101. </html> 

非遞迴演算法略微複雜些,程式碼裡面有詳細的註釋和說明。

這裡還有一個使用JavaScript陣列物件的push()和pop()方法的非遞迴版本,可以簡化程式,道理是一樣的。
非遞迴演算法版本2: 


  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>Hanoi Tower (Non-recursive algorithm, Version 2) - 漢諾塔(非遞迴演算法,版本2)</title> 
  5.     <style type="text/css"> 
  6.         i  
  7.         {  
  8.             color: #ff0080;  
  9.         }  
  10.         p  
  11.         {  
  12.             text-align: center;  
  13.         }  
  14.     </style> 
  15.     <!--  
  16. 漢諾塔非遞迴演算法:  
  17. (1)、若問題規模n為偶數,按順時針方向依次擺放s, m, d為環,若n為奇數,則為s, d, m;  
  18. (2)、將盤子由小到大逐個編號並放置在s上,即最小的盤子編號為1,放在最上面;  
  19. (3)、按順時針方向把編號為1的盤子從當前的位置移動到下一位置;  
  20. (4)、把另外兩個位置(即除去1號盤子所在的位置)中非空的那個上的一個圓盤移到空的位置上,如果另外兩個位置都非空,則移動編號較小的那一個;  
  21. (5)、重複進行(3)和(4),直到移動的次數等於2^n-1。  
  22. --> 
  23.     <script type="text/javascript"> 
  24.         var step = 0;  
  25.         function MoveOnePlate(n, loca1, loca2) {  
  26.             document.write("第" + ++step + "步:移動第<i>" + n + "</i>個盤子,從" + loca1 + "到" + loca2 + "<br />");  
  27.         }  
  28.  
  29.         function MovePlates(n, s, m, d) {  
  30.             //定義位置  
  31.             var loop = new Array([], [], []);  
  32.             //定義位置描述字串陣列(n為偶數的情況)  
  33.             var loca = new Array(s, m, d);  
  34.             if (n % 2 != 0) //n為奇數的情況  
  35.             {  
  36.                 loca[1] = d;  
  37.                 loca[2] = m;  
  38.             }  
  39.             //初始化源位置上的盤子  
  40.             for (var i = 0; i < n; i++)  
  41.                 loop[0].push(n - i);  
  42.             var count = Math.pow(2, n) - 1; //移動次數,即迴圈退出條件  
  43.             var firstPlate = 0; //1號盤子的位置  
  44.             do {  
  45.                 //將1號盤子順時針移動到後1個位置  
  46.                 MoveOnePlate(1, loca[firstPlate], loca[(firstPlate + 1) % 3]); //顯示移動過程  
  47.                 loop[(firstPlate + 1) % 3].push(loop[firstPlate].pop()); //從舊位置移動到新位置  
  48.                 firstPlate = (firstPlate + 1) % 3; //修改1號盤子的位置  
  49.                 count--; //記錄移動次數  
  50.                 //移動另外的兩個位置上的盤子  
  51.                 if (count != 0) //避免最後一次移動後仍然移動而導致錯誤  
  52.                 {  
  53.                     //確定另外兩個位置如何移動  
  54.                     if (loop[(firstPlate + 1) % 3].length == 0 || loop[(firstPlate + 2) % 3].length != 0 && loop[(firstPlate + 2) % 3][loop[(firstPlate + 2) % 3].length - 1] < loop[(firstPlate + 1) % 3][loop[(firstPlate + 1) % 3].length - 1]) {  
  55.                         //1號盤子的後第1個位置為空,或者無空位置且1號盤子後第2個位置編號較小,此時將1號盤子後第2個位置的盤子移動到1號盤子後第1個位置上  
  56.                         MoveOnePlate(loop[(firstPlate + 2) % 3][loop[(firstPlate + 2) % 3].length - 1], loca[(firstPlate + 2) % 3], loca[(firstPlate + 1) % 3]); //顯示移動過程  
  57.                         loop[(firstPlate + 1) % 3].push(loop[(firstPlate + 2) % 3].pop()); //從舊位置移動到新位置  
  58.                     }  
  59.                     else {  
  60.                         //1號盤子的後第2個位置為空,或者無空位置且1號盤子後第1個位置編號較小,此時將1號盤子後第1個位置的盤子移動到1號盤子後第2個位置上  
  61.                         MoveOnePlate(loop[(firstPlate + 1) % 3][loop[(firstPlate + 1) % 3].length - 1], loca[(firstPlate + 1) % 3], loca[(firstPlate + 2) % 3]); //顯示移動過程  
  62.                         loop[(firstPlate + 2) % 3].push(loop[(firstPlate + 1) % 3].pop()); //從舊位置移動到新位置  
  63.                     }  
  64.                     count--; //記錄移動次數  
  65.                 }  
  66.             } while (count != 0)  
  67.         }  
  68.     </script> 
  69. </head> 
  70. <body> 
  71.     <p> 
  72.         Hanoi Tower (Non-recursive algorithm, Version 2) - 漢諾塔(非遞迴演算法,版本2)<br /> 
  73.         Mengliao Software Studio(Baiyu) - 夢遼軟體工作室(白宇)<br /> 
  74.         Copyright 2011, All right reserved. - 版權所有(C) 2011<br /> 
  75.         2011.04.04</p> 
  76.     <script type="text/javascript"> 
  77.         n = parseInt(prompt("請輸入盤子的數量:", 3), 10);  
  78.         if (isNaN(n) || n < 1 || n > 16) {  
  79.             alert("請輸入介於1到16的自然數!");  
  80.             location.reload();  
  81.         }  
  82.         else {  
  83.             document.write("共" + n + "個盤子,需移動" + (Math.pow(2, n) - 1) + "步:<br /><br />");  
  84.             MovePlates(n, "<i>源點</i>", "<i>臨時</i>", "<i>目標</i>");  
  85.         }  
  86.     </script> 
  87. </body> 
  88. </html> 

這裡是三個演算法網頁原始檔的連線:
http://mengliao.blog.51cto.com/attachment/201104/876134_1301907387.rar










本文轉自 BlackAlpha 51CTO部落格,原文連結:http://blog.51cto.com/mengliao/534053,如需轉載請自行聯絡原作者

相關文章