【题目】
一位农夫带着一只狼,一只羊和一些菜过河。河边只有一条船,由于船太小,只能装下农夫和他的一样东西。在无人看管的情况下,狼要吃羊,羊要吃菜,请问农夫如何才能用最快的方法将这三样东西平安过河。
【问题分析】
总论:
农夫、狼、羊、菜可以用一个四元组来表示,他们要么在河这边,要么在河那边,只有两种状态,所以最适合用布尔代数来进行分析。
步骤1:分析题目中的输入、输出
我们用2#xxxx这4个比特位数字来表示四元组,从高到低位分别表示农夫(b3)、狼(b2)、羊(b1)、菜(b0),每个bit位可为0或1,由此得到2#0000、
2#0001……2#1110、2#1111这16种状态,0表示这河这边,1表示在河对岸,因此初始状态为2#0000,即所有物体都在河这边,终止状态为2#1111,即所
有物体都到了河对岸。
步骤2:分析题干中的约束条件
约束条件1:“船太小,只能装下农夫和他的一样东西”,意味着狼、羊、菜每次最多只能有一个物体的状态发生变化,即从状态2#x101切换到状态
2#x100是合法的(只有菜的状态发生了变化),而从状态2#x101切换到状态2#x000是非法的(狼和菜的状态都发生了变化)
约束条件2:“在无人看管的情况下,狼要吃羊,羊要吃菜”,意味着b2与b1不能同为0或1,b1与b0也不能同为0或1,除非它们与b3的符号一致,例如
状态2#0110是非法的(狼与羊在同一侧),而状态2#1110是合法的(农夫、狼与羊都在同一侧)。
约束条件3:这是一个隐含的约束条件,农夫的状态必须在连续的切换,即状态2#0010到状态2#1010是合法的,到状态2#0011是非法的。
步骤3:分析合法状态
根据约束条件2,我们分析得出有10种合法状态,可分为两组,分别代表在河这边和在河对岸,用字母表示如下
Start=2#0000 A=2#0001 B=2#0010 C=2#0100 D=2#0101
E=2#1010 F=2#1011 G=2#1101 H=2#1110 End=2#1111
步骤4:分析状态切换
根据约束条件1和约束条件3,我们可以分析得到每一个状态的下一个合法状态的集合,如下所示
Start->E A->F|G B->E|F|H C->G|H D->G|End
E->B F->A|B G->A|C|D H->B|C End->D
步骤5:分析状态图
根据上面的状态切换,我们可以绘制从Start到End的状态图
Start--E--B--F--A--G--END
| |
H--------------C
从图中可得知,从状态Start到状态End有两条可达路径,Start-E-B-H-C-G-END为最短路径
结论:
问题最终转化为从起始状态到最终状态是否存在连通图,如果存在,求最短路径
【解决方案】
设计了两个进程,分别为主进程和计算进程,主进程派生计算进程,接收计算进程返回的可达路径消息并打印出来,计算进程用于计算可达路径。
主进程的代码如下所示:
1 -module(farmercrossriver).
2 -export([start/0, calu_route/0]).
3
4 %% 4位二进制数比特位的顺序:农夫、狼、羊、菜,0:左岸,1:右岸
5
6 -define(Start, 2#0000). %起点
7 -define(End, 2#1111). %终点
8 -define(A, 2#0001). %可达的节点
9 -define(B, 2#0010).
10 -define(C, 2#0100).
11 -define(D, 2#0101).
12 -define(E, 2#1010).
13 -define(F, 2#1011).
14 -define(G, 2#1101).
15 -define(H, 2#1110).
16 -define(AllNodes, [?Start, ?End, ?A, ?B, ?C, ?D, ?E, ?F, ?G, ?H]).
17
18 %% 启动
19 start() ->
20 register(?MODULE, self()),
21 spawn(farmercrossriver, calu_route, []), %派生计算进程
22 recv_answer([]).
23
24 %% 接收计算出的结果
25 recv_answer(AllAvailRoutes) ->
26 receive
27 {ok, AvailRoute} -> %计算出一条可达路径
28 recv_answer([AvailRoute] ++ AllAvailRoutes);
29 stop -> %全部计算完毕
30 io:format("avail ~w routes~n", [length(AllAvailRoutes)]),
31 io:format("routes:~w~n", [AllAvailRoutes])
32 end.
计算进程的代码如下所示:
1 %% 计算可达的路径
2 calu_route() ->
3 calu_route([], ?Start),
4 ?MODULE ! stop. %结束计算
5
6 %% PassedNodes:已经走过的节点
7 %% NextNode:下一个可达的节点
8 calu_route(PassedNodes, ?End) -> %到达终点
9 ?MODULE ! {ok, lists:reverse([?End] ++ PassedNodes)}; %向主进程发送可达路径
10 calu_route(PassedNodes, NextNode) ->
11 IsMember = lists:member(NextNode, PassedNodes),
12 if
13 IsMember =:= false -> %%没有构成回路
14 NeighborNodes = calu_neighbor_nodes(NextNode, [], ?AllNodes), %计算当前节点的邻接节点
15 lists:foreach(fun(Node) -> calu_route([NextNode] ++ PassedNodes, Node) end, NeighborNodes);
16 true ->
17 stop
18 end.
19
20 %% 计算邻接节点
21 calu_neighbor_nodes(_, NeighborNodes, []) ->
22 NeighborNodes;
23 calu_neighbor_nodes(Node, NeighborNodes, WaitCaluNodes) ->
24 [NewNode | OtherNodes] = WaitCaluNodes,
25 Value = Node bxor NewNode, %比特位的异或运算,用来计算有几个物体的位置发生的变更
26 <<F:1, W:1, S:1, V:1>> = <<Value:4>>, %提取各个物体位置变更的情况
27 if
28 (F=:= 1) and (W+S+V =< 1) -> %农夫的位置必须发生变更,且其它物体每次最多一个能发生位置变更
29 calu_neighbor_nodes(Node, [NewNode] ++ NeighborNodes, OtherNodes);
30 true ->
31 calu_neighbor_nodes(Node, NeighborNodes, OtherNodes)
32 end.