程式設計也快樂第三期解答(四)
上篇文章中的演算法3
在給定問題有解的情況下一定可以找到解答,但它給出的解答的操作步驟不一定是最少的。 現在讓我們來看看演算法4
吧。
水(Water.cs)
1: using System;
2: using System.IO;
3: using System.Linq;
4: using System.Diagnostics;
5: using System.Collections.Generic;
6: using Skyiv.Extensions;
7:
8: namespace Skyiv.Ben.Pond
9: {
10: sealed class Water
11: {
12: TextWriter writer; // 往哪寫?
13: int target; // 要達到的目標
14: Kettle[] kettles; // 水壺們: 最少兩個
15: List<string> actions; // 記錄解答步驟的列表
16: List<int[]> currents; // 記錄各水壺水量的列表
17:
18: public Water(TextWriter writer, int limit, int target, params int[] capacitys)
19: { // 建構函式:本演算法無視步數限制,所以忽略了(limit)引數
20: this.writer = writer;
21: this.target = target;
22: if (target < 0) throw new ArgumentException("目標水量不能小於零");
23: kettles = new Kettle[capacitys.Length];
24: for (var i = 0; i < capacitys.Length; i++)
25: {
26: if (capacitys[i] < 0) throw new ArgumentException("水壺的容量不能小於零");
27: kettles[i] = new Kettle(((char)('A' + i)).ToString(), capacitys[i]);
28: }
29: actions = new List<string>();
30: currents = new List<int[]>();
31: }
32:
33: public void Solve()
34: { // 演算法4:只使用前兩隻水壺,試圖尋找最少操作步驟的解
35: Trace.Listeners.Add(new TextWriterTraceListener(writer)); // -d:TRACE
36: writer.Write("目標:{0} 水壺們:", target);
37: foreach (var kettle in kettles) writer.Write(kettle);
38: writer.WriteLine(); // 以上三行輸出題目的條件
39: if (HasSolution())
40: {
41: var n = GetTargetKettle();
42: var count = Solve(n < 0 ? 0 : n);
43: if (n < 0 && count < Solve(1)) Solve(0);
44: Report();
45: }
46: else writer.WriteLine("*** 無解(本演算法僅使用前兩個水壺) ***");
47: }
48:
49: int Solve(int n)
50: { // 尋找解決方案,返回所需操作步驟的數目
51: SetTargetKettle(n);
52: while (!kettles[0].IsEmpty || !kettles[1].IsEmpty)
53: { // 往回倒推,如果最後得到兩隻空壺就成功了
54: Trace.Assert(kettles[0].IsEmpty || kettles[1].IsEmpty);// 剛好一壺為空
55: if (kettles[1].IsEmpty && kettles[0].IsFull) { Clean(0); break; }
56: if (kettles[0].IsEmpty && kettles[1].IsFull) { Clean(1); break; }
57: int i = kettles[0].IsEmpty ? 0 : 1, j = 1 - i;
58: FullUp(i); // 挑選一隻空壺,裝滿之 (此時必有一壺為空,另一壺不空也不滿)
59: while (!kettles[i].IsEmpty)
60: { // 從剛裝滿的壺倒入另一壺,直到本壺為空
61: Move(i, j);
62: if (kettles[j].IsFull) Clean(j); // 如另一壺滿了,則倒空之
63: }
64: }
65: return StepCount();
66: }
67:
68: bool HasSolution()
69: { // 檢查是否有解
70: var imax = (kettles[0].Capacity > kettles[1].Capacity) ? 0 : 1;
71: var gcd = MathExtensions.Gcd(kettles[0].Capacity, kettles[1].Capacity);
72: return (gcd == 0 || target % gcd == 0) && target <= kettles[imax].Capacity;
73: }
74:
75: int GetTargetKettle()
76: { // 目標水量應設定到哪隻壺裡? (-1)表示未定
77: var min = (kettles[0].Capacity < kettles[1].Capacity) ? 0 : 1;
78: return (target > kettles[min].Capacity) ? (1 - min)
79: : (target == 0 || target == kettles[min].Capacity) ? min : -1;
80: }
81:
82: void SetTargetKettle(int n)
83: { // 設定(n)壺的水量為目標水量,並做些準備工作
84: kettles[n].FullUp();
85: kettles[n].Move(new Kettle(null, kettles[n].Capacity - target));
86: actions.Clear();
87: currents.Clear();
88: Mark(null); // 記錄最終完成時的各水壺的水量
89: }
90:
91: int StepCount()
92: { // 返回操作步驟的數目
93: var i = Math.Max(0, actions.Count - 1);
94: while (i >= 0 && currents[i][0] != target && currents[i][1] != target) i--;
95: return actions.Count - i;
96: }
97:
98: void FullUp(int n)
99: { // 倒推步驟:裝滿(n)壺,解答步驟:倒空(n)壺
100: kettles[n].FullUp();
101: Mark("倒空" + kettles[n].Name);
102: }
103:
104: void Clean(int n)
105: { // 倒推步驟:倒空(n)壺,解答步驟:裝滿(n)壺
106: kettles[n].Clean();
107: Mark("裝滿" + kettles[n].Name);
108: }
109:
110: void Move(int n, int m)
111: { // 倒推步驟:從(n)壺倒入(m)壺,解答步驟:從(m)壺倒入(n)壺
112: kettles[n].Move(kettles[m]);
113: Mark("從" + kettles[m].Name + "倒入" + kettles[n].Name);
114: }
115:
116: void Mark(string action)
117: { // 記錄當前步驟和各水壺的水量
118: if (action != null) actions.Add(action);
119: currents.Add(kettles.Select(x => x.Current).ToArray());
120: }
121:
122: void Report()
123: { // 報告解答步驟
124: for (var i = actions.Count - 1; i >= 0; i--)
125: {
126: writer.Write("{0, 3}: ", actions.Count - i);
127: writer.Write("{0} (", actions[i].ChinesePadRight(8));
128: foreach (var current in currents[i]) writer.Write("{0,2} ", current);
129: writer.WriteLine(")"); // 下一行不是必須的,只是為了縮短解答步驟
130: if (currents[i][0] == target || currents[i][1] == target) break;
131: }
132: writer.WriteLine("---- 大功告成 --------");
133: }
134: }
135: }
這個Water
類的核心內容其實還是演算法3
的那一套,也是往回倒推,大部分內容都是相同的。
- 第 68~73 行的
HasSolution
方法檢查是否有解。 - 第 75~80 行的
GetTargetKettle
方法決定目標水量應設定到哪隻壺裡。- 如果目標水量大於小壺的容量,則只能設定到大壺裡。(第 78 行)
- 如果目標水量等於零或者等於小壺的容量,則設定到小壺裡。(第 79 行)
- 否則目標水量就一定大於零而小於小壺的容量,我們無法決定應該設定到哪隻壺裡。(第 79 行)
- 第 82~89 行的
SetTargetKettle
方法設定指定的壺的水量為目標水量,然後清除記錄解答步驟和各水壺水量的列表、記錄最終完成時各水壺的水量。 - 第 91~96 行的
StepCount
計算已經求解出來的操作步驟的數目。(我懷疑執行到第 95 行時i
值總是 1 或者 0(當 actions.Count == 0 時),不過沒有證據) - 第 33~47 行的
Solve
方法是主控程式,根據需要呼叫後面的過載的Solve
方法 0~3 次,然後報告執行結果。- 如果判斷出給定的問題無解,則直接報告。(第 39 行、第 46 行)
- 否則,先獲取目標水量應設定到哪隻壺裡。(第 41 行)
- 如果明確知道目標水量應設定到哪隻壺裡,直接呼叫過載的
Solve
方法解答之。(第 42 行) - 否則,先試著將目標水量設定到到第一隻壺中,呼叫過載的
Solve
方法得到操作步驟的數目。(第 42 行) - 接著試著將目標水量設定到第二隻壺中,再次呼叫過載的
Solve
方法得到操作步驟的數目。(第 43 行) - 如果前者小於後者,只好第三次呼叫過載的
Solve
方法以得到前者對應的操作步驟。(第 43 行) - 否則就沒有必要再幹什麼了,直接報告答案就行了。(第 44 行)
- 第 49~66 行的過載的
Solve
方法基本上和演算法3
中相同。只不過在往回倒推之前先設定目標水量到指定的壺中,計算完畢後再返回操作步驟的數目給呼叫者。 - 這個程式的其他方面和
演算法3
是一樣的。
編譯和執行
Water$ make
dmcs -out:ConsoleRunner.exe -d:TRACE ConsoleRunner.cs Water.cs Kettle.cs StringExtensions.cs MathExtensions.cs
$ mono ConsoleRunner.exe 3 5 6
目標:3 水壺們:[A:0/5][B:0/6]
1: 裝滿A ( 5 0 )
2: 從A倒入B ( 0 5 )
3: 裝滿A ( 5 5 )
4: 從A倒入B ( 4 6 )
5: 倒空B ( 4 0 )
6: 從A倒入B ( 0 4 )
7: 裝滿A ( 5 4 )
8: 從A倒入B ( 3 6 )
---- 大功告成 --------
可以看到,同樣的輸入條件(目標水量 3,水壺容量分別為 5 和 6),本演算法只需要 8 步,而演算法3
需要 10 步。再看一個例子:
$ mono ConsoleRunner.exe 3 4 7
目標:3 水壺們:[A:0/4][B:0/7]
1: 裝滿B ( 0 7 )
2: 從B倒入A ( 4 3 )
---- 大功告成 --------
從上面兩個例子中可以看出,在目標水量大於零而小於小壺容量的情況下,為了求得最少操作步驟的解,有時需要將目標水量設定到大壺中,有時又需要將目標水量設定到小壺中。
相關文章
- 程式設計也快樂第三期解答(三)程式設計
- 程式設計也快樂第三期解答(二)程式設計
- 程式設計也快樂第三期解答(一)程式設計
- 程式設計也快樂第3期SQL程式碼程式設計SQL
- 程式設計也快樂: 兩隻水壺 C程式碼 搜尋版程式設計C程式
- 程式設計師節快樂程式設計師
- 使用Google Guava快樂程式設計GoGuava程式設計
- 程式設計師的快樂生活程式設計師
- 快樂指南:程式設計師版程式設計師
- 遊戲設計-遊戲快樂知多少(四) ?-驚喜遊戲設計
- 1024!程式設計師節快樂!程式設計師
- 女程式設計師們!節日快樂!程式設計師
- 四大遊戲程式設計網站,邊玩遊戲,邊學Python,拒絕枯燥快樂程式設計遊戲程式設計網站Python
- 程式設計師如何祝自己生日快樂程式設計師
- 程式設計學習之路:痛並快樂著程式設計
- 程式設計師的快樂:那些小細節程式設計師
- 讓程式設計快樂起來的過程程式設計
- 程式設計師快樂器之JAVA程式碼生成工具程式設計師Java
- 編碼也快樂:兩隻水壺F#程式
- 編碼也快樂:兩隻水壺Scheme程式Scheme
- 編碼也快樂:兩隻水壺C#程式C#
- 華為大佬:做一個快樂的程式設計師程式設計師
- 調查:是什麼讓程式設計師快樂?程式設計師
- 快樂Node程式設計師的10個習慣程式設計師
- 《Ruby基礎教程(第4版)》:快樂程式設計程式設計
- 在程式設計中體驗純粹的快樂程式設計
- 編碼也快樂:兩水壺的故事之JS程式JS
- 五線譜入門,程式設計師也可以玩音樂程式設計師
- 快樂的星期天:Scratch少兒趣味程式設計程式設計
- 程式設計師保持天天快樂的6個習慣程式設計師
- 程式設計師的燈下黑:沒學會快樂程式設計師
- 學習三個月,快樂小四年——感謝風變程式設計Python課程!程式設計Python
- 傳播正能量——做一個快樂的程式設計師程式設計師
- 《快樂碼農》第5期 大話程式設計師面試程式設計師面試
- 編碼也快樂活動:撲克牌排序排序
- 編碼也快樂!撲克牌排序JAVA排序Java
- Java程式設計師面試題及解答Java程式設計師面試題
- 也談程式設計改革程式設計