程式設計也快樂第三期解答(二)

黃志斌發表於2013-04-11

多個水壺的情況

昨天的文章中,我們的演算法只處理前兩個水壺。現在讓我們修改演算法,處理輸入的所有水壺。下面就是修改後的Water.cs

 1: using System;
 2: using System.IO;
 3: using Skyiv.Extensions;
 4: 
 5: namespace Skyiv.Ben.Pond
 6: {
 7:   sealed class Water
 8:   {
 9:     TextWriter writer; // 往哪寫?
10:     int limit;         // 解決方案的步數限制
11:     int target;        // 要達到的目標
12:     Kettle[] kettles;  // 水壺們: 最少兩個
13:     
14:     public Water(TextWriter writer, int limit, int target, params int[] capacitys)
15:     { // 建構函式
16:       this.writer = writer;
17:       this.limit = limit;
18:       this.target = target;
19:       kettles = new Kettle[capacitys.Length];
20:       for (var i = 0; i < capacitys.Length; i++)
21:         kettles[i] = new Kettle(((char)('A' + i)).ToString(), capacitys[i]);
22:     }
23:     
24:     public void Solve()
25:     { // 尋找解決步驟
26:       writer.Write("步數限制:{0} 目標:{1} 水壺們:", limit, target);
27:       foreach (var kettle in kettles)  writer.Write(kettle);
28:       writer.WriteLine(); // 以上三行輸出題目的條件
29:       for (int i = 0, k = 1; ; k = -k)
30:       { // 主迴圈,演算法2
31:         if (k > 0) Solve(++i, kettles[kettles.Length - 1].FullUp);
32:         else Solve(++i, kettles[0].Clean);
33:         for (var j = kettles.Length - 1; j > 0; j--)
34:           Solve(++i, kettles[j].Move, kettles[j - 1]);
35:       }
36:     }
37: 
38:     void Solve(int step, Func<string> func)
39:     { // 適用於無引數動作:倒空水壺、裝滿水壺
40:       Solve(step, func());
41:     }  
42:     
43:     void Solve(int step, Func<Kettle, string> func, Kettle other)
44:     { // 適用於一個引數的動作:將水倒入另一隻壺中
45:       Solve(step, func(other));
46:     }  
47:     
48:     void Solve(int step, string func)
49:     { // 解決步驟的一步
50:       writer.Write("{0,3}: {1}", step, func.ChinesePadRight(14));
51:       foreach (var kettle in kettles) writer.Write(kettle);
52:       writer.WriteLine();
53:       var isFinished = IsFinished();
54:       if (step < limit && !isFinished) return;
55:       if (isFinished) writer.WriteLine("---- 大功告成 ---------------");
56:       else if (step >= limit) writer.WriteLine("**** 出師未捷身先死 ****");
57:       Environment.Exit(0);
58:     }
59:     
60:     bool IsFinished()
61:     { // 是否完成任務?
62:       foreach (var kettle in kettles)
63:         if (kettle.Current == target) return true;
64:       return false;
65:     }
66:   }
67: }

注意,對比昨天的演算法1,我們今天的演算法2只修改了Water.cs原始檔中第 29~35 行的主迴圈,其餘程式碼都沒有變動。其餘的 C# 檔案也沒有改動。我們今天的演算法2在主迴圈中反覆執行以下步驟:

  • 裝滿最後一個水壺。
  • 將水從最後一個水壺依次倒入前一個水壺,直至第一個水壺。
  • 倒空第一個水壺。
  • 將水從最後一個水壺依次倒入前一個水壺,直至第一個水壺。

直到完成任務,或者達到步數限制。可以看出,如果只有兩個水壺,這個演算法2演算法1是一樣的。

編譯和執行

好了,讓我們來編譯和執行吧:

Water$ make && mono ConsoleRunner.exe 3 5 6
dmcs -out:ConsoleRunner.exe ConsoleRunner.cs Water.cs Kettle.cs StringExtensions.cs
步數限制:20 目標:3 水壺們:[A:0/5][B:0/6]
  1: [裝滿:B]      [A:0/5][B:6/6]
  2: [從B倒入A:5]  [A:5/5][B:1/6]
  3: [倒空:A]      [A:0/5][B:1/6]
  4: [從B倒入A:1]  [A:1/5][B:0/6]
  5: [裝滿:B]      [A:1/5][B:6/6]
  6: [從B倒入A:4]  [A:5/5][B:2/6]
  7: [倒空:A]      [A:0/5][B:2/6]
  8: [從B倒入A:2]  [A:2/5][B:0/6]
  9: [裝滿:B]      [A:2/5][B:6/6]
 10: [從B倒入A:3]  [A:5/5][B:3/6]
---- 大功告成 ---------------

這是兩個水壺的情況,不出所料,執行結果和昨天演算法1的一樣。 注意在這裡我們用&&連線makemono命令,在同一個命令列編譯和執行,在除錯程式時很方便。如果編譯出錯的話,後面的執行命令是不會執行的。下面是更多的水壺的執行例項:

Water$ mono ConsoleRunner.exe 8 2 5 7 12
步數限制:20 目標:8 水壺們:[A:0/2][B:0/5][C:0/7][D:0/12]
  1: [裝滿:D]      [A:0/2][B:0/5][C:0/7][D:12/12]
  2: [從D倒入C:7]  [A:0/2][B:0/5][C:7/7][D:5/12]
  3: [從C倒入B:5]  [A:0/2][B:5/5][C:2/7][D:5/12]
  4: [從B倒入A:2]  [A:2/2][B:3/5][C:2/7][D:5/12]
  5: [倒空:A]      [A:0/2][B:3/5][C:2/7][D:5/12]
  6: [從D倒入C:5]  [A:0/2][B:3/5][C:7/7][D:0/12]
  7: [從C倒入B:2]  [A:0/2][B:5/5][C:5/7][D:0/12]
  8: [從B倒入A:2]  [A:2/2][B:3/5][C:5/7][D:0/12]
  9: [裝滿:D]      [A:2/2][B:3/5][C:5/7][D:12/12]
 10: [從D倒入C:2]  [A:2/2][B:3/5][C:7/7][D:10/12]
 11: [從C倒入B:2]  [A:2/2][B:5/5][C:5/7][D:10/12]
 12: [從B倒入A:0]  [A:2/2][B:5/5][C:5/7][D:10/12]
 13: [倒空:A]      [A:0/2][B:5/5][C:5/7][D:10/12]
 14: [從D倒入C:2]  [A:0/2][B:5/5][C:7/7][D:8/12]
---- 大功告成 ---------------

要注意的是,這個演算法2同昨天的演算法1一樣,也不是一定可以找到解答的。如果找到解答,也不一定是最優的。

相關文章