三水桶等分8升水的问题
参考书:《算法的乐趣》王晓华著
问题描述
有三个容积分别是 3 升、5 升和 8 升的水桶,其中容积为8升的水桶中装满水,容积为 3 升和容积为 5 升的水桶是空的。三个水桶都没有体积刻度,现在需要将大水桶中的8升水等分成两份,每份都是4升水,附加条件是只能使用另外两个空水桶,不能使用其他容器。
好像是小学的暑假作业中的题目,答案就不说了,主要分析如何用代码来解题。用人的思维方式来解题是如何通过倒水确定 1 升水,就能找到最终答案了。代码解题的基本思路是穷尽法,使用状态树模型来求解。
重点提示
代码实现
class Chapter5_853
{
const string full_state = "853";
List listProcess = new List();
public void Start()
{
listProcess.Add("800");
SearchState();
}
private void SearchState()
{
string cur_state = listProcess[listProcess.Count-1];
if (listProcess.Count>1 && cur_state == "440")
{
PrintResult();
return;
}
for (int from = 0; from < 3; from++)
{
for (int to = 0; to < 3; to++)
{
if (to == from)
{
//不能自己给自己倒水
continue;
}
SearchStateOnAction(cur_state, from, to);
}
}
}
private void SearchStateOnAction(string cur_state, int from, int to)
{
char[] next_state = new char[] { cur_state[0], cur_state[1], cur_state[2] };
if (cur_state[from] == '0' || cur_state[to] == full_state[to]) return;
int warter = (cur_state[from]-'0' < (full_state[to] - cur_state[to])) ? cur_state[from]-'0' : (full_state[to] - cur_state[to]);
next_state[from] = (char)((int)cur_state[from] - warter);
next_state[to] = (char)((int)cur_state[to] + warter);
string next_str = string.Format("{0}{1}{2}", next_state[0], next_state[1], next_state[2]);
if (!listProcess.Contains(next_str))
{
listProcess.Add(next_str);
SearchState();
listProcess.RemoveAt(listProcess.Count - 1);
}
}
private void PrintResult()
{
for (int i = 0; i < listProcess.Count; i++)
{
if (i>0)
{
//输出操作方法
int[] from = new int[] { Convert.ToInt32(listProcess[i - 1][0]), Convert.ToInt32(listProcess[i - 1][1]), Convert.ToInt32(listProcess[i - 1][2]) };
int[] to = new int[] { Convert.ToInt32(listProcess[i][0]), Convert.ToInt32(listProcess[i][1]), Convert.ToInt32(listProcess[i][2]) };
int from_index=-1, to_index=-1 , water = -1;
for (int j = 0; j < 3; j++)
{
if (from[j] > to[j])
{
from_index = j;
water = from[j] - to[j];
}
else if (from[j] < to[j])
{
to_index = j;
water = to[j] - from[j];
}
}
Console.WriteLine("{0}->{1}:{2}", from_index, to_index, water);
}
Console.WriteLine(listProcess[i]);
}
}
}
//调用代码
static void Main(string[] args)
{
Chapter5_853 process = new Chapter5_853();
process.Start();
}
整个代码根据书本上的介绍进行编写,并没有说先构造一个状态数,而是通过 深度优先的搜索算法,搜索结果和构建状态数是同步进行的。个人觉点比较重要的代码段:
if (!listProcess.Contains(next_str))
{
listProcess.Add(next_str);
SearchState();
listProcess.RemoveAt(listProcess.Count - 1);
}
这段代码是深度优先搜索方法的关键点。
和这个算法类似的是第6章,妖怪和和尚过河问题
问题描述
有三个和尚和三个妖怪,有一条船能坐两个人,他们需要使用这条船过河。限制条件是,在两边的岸上和船上,妖怪的数量不能大于和尚的数量(妖怪会把和尚吃掉)。
重点提示
状态的数学模型中需要把船的位置也包含进去。船上面就两个人也就不存在妖怪数量大于和尚数量的情况,但是一定至少有一个人船才能到另一边。
个人实现代码
class Chapter6_323
{
private bool bLocal = true;
int count = 0;
private List list_states = new List();
public void Start()
{
bLocal = true;
//Console.WriteLine("+" + "33001");
list_states.Add("33001");
SearchState();
}
private void SearchState()
{
string cur_state = list_states[list_states.Count - 1];
if (cur_state == "00330")
{
count++;
PrintResult(count);
return;
}
List list_act = new List();
if (bLocal)
{
int local_monster = cur_state[0] - '0';
int local_monk = cur_state[1] - '0';
for (int i = 0; i <= local_monster; i++)
{
for (int j = (2 - i)< local_monk?(2-i): local_monk; j>=0; j--)
{
if (i == 0 && j == 0) continue;
list_act.Add(string.Format("{0}{1}", i, j));
}
}
}
else
{
int remote_monster = cur_state[2] - '0';
int remote_monk = cur_state[3] - '0';
for (int i = 0; i <= remote_monster; i++)
{
for (int j = (2 - i) < remote_monk ? (2 - i) : remote_monk; j >= 0; j--)
{
if (i == 0 && j == 0) continue;
list_act.Add(string.Format("{0}{1}", i, j));
}
}
}
foreach (string item_act in list_act)
{
string next_state = "";
if (cur_state == "1320")
{
int s = 1;
}
if (MackActionNewState(cur_state, item_act, out next_state))
{
//Console.WriteLine("+" + next_state);
bLocal = !bLocal;
list_states.Add(next_state);
SearchState();
list_states.RemoveAt(list_states.Count - 1);
bLocal = !bLocal;
//Console.WriteLine("-" + next_state);
}
}
}
private bool MackActionNewState(string cur_state,string try_action,out string next_state)
{
next_state = cur_state;
bool result = false;
int local_monster = cur_state[0] - '0';
int local_monk = cur_state[1] - '0';
int remote_monster = cur_state[2] - '0';
int remote_monk = cur_state[3] - '0';
int move_monster = try_action[0]-'0';
int move_monk = try_action[1] - '0';
if (bLocal)
{
local_monster -= move_monster;
local_monk -= move_monk;
remote_monster += move_monster;
remote_monk += move_monk;
}
else
{
local_monster += move_monster;
local_monk += move_monk;
remote_monster -= move_monster;
remote_monk -= move_monk;
}
//直接检查是否有效
//和尚是否会被妖怪吃掉
if ((local_monk > 0 && local_monster > local_monk) || (remote_monk > 0 && remote_monster > remote_monk))
{
result = false;
}
else
{
result = true;
}
//是否回到之前的状态
if (result)
{
//这里表示下一个状态,所以最后一位和bLocal是相反值,表示船的下一个状态的位置
next_state = string.Format("{0}{1}{2}{3}{4}", local_monster, local_monk,remote_monster,remote_monk, (!bLocal)?'1':'0');
}
if (list_states.Contains(next_state))
{
result = false;
}
return result;
}
private void PrintResult(int count)
{
Console.WriteLine("方案{0}:",count);
foreach (string item in list_states)
{
Console.WriteLine(item);
}
}
}
把这段代码的注释打开就可以看到遍历时的过程:
//Console.WriteLine("+" + next_state);
bLocal = !bLocal;
list_states.Add(next_state);
SearchState();
list_states.RemoveAt(list_states.Count - 1);
bLocal = !bLocal;
//Console.WriteLine("-" + next_state);
和书不太一样的地方是,我把次过河的情况先缩小了范围,然后再取判断限制条件和是否形成回路。