问题描述:
有三个和尚和三个妖怪,他们要利用唯一一条小船过河,这条小船一次最多只能载两个人,同时,无论是在河的两岸还是船上,只要妖怪的数量大于和尚的数量,妖怪们就会将和尚吃掉。现在需要选择一种过河的安排,保证和尚和妖怪都能过河且和尚不能被妖怪吃掉。
问题分析:
将和尚和妖怪们的状态抽象为一个数组
$status = array( ‘left_missionary_num‘, //左岸和尚数量 ‘left_monster_num‘, //左岸妖怪数量 ‘right_missionary_num‘, //右岸和尚数量 ‘right_monster_num‘ //右岸妖怪数量 )
初始状态为array(3,3,0,0),并且船在左岸,最终状态是array(0,0,3,3),此时船在右岸。
船的左右移动会推动状态的转变,用-1表示船在左岸,用1表示船在右岸,当船靠岸时,船上的所有和尚或者妖怪要下船。
为了保证和尚不被妖怪吃掉,则需要满足以下约束:
$status[‘left_missionary_num‘] >= $status[‘left_monster_num‘],表示左岸和尚的数量>=左岸妖怪的数量
$status[‘right_missionary_num‘] >= $status[‘right_monster_num‘],表示右岸和尚的数量>=右岸妖怪的数量
3 - $status[‘left_missionary_num‘] - $status[‘right_missionary_num‘] >= 3 - $status[‘left_monster_num‘] - $status[‘right_monster_num‘],表示船上的和尚数量>=船上的妖怪数量,因为船上最多只有2个位置,所以这个条件是默认满足的。
状态的搜索:
每一次状态转变时:
上船:要根据船的位置来判断是左岸还是右岸的和尚或妖怪上船。
下船:要根据船的位置来判断船上的和尚或妖怪下船后会加入左岸还是右岸。
下船后,要符合上面提到的约束。
并且上船后,要保证岸边的和尚或者妖怪的数量都要 >= 0。
每个状态可能会有多个下一个状态,在不考虑约束的情况下,总共会有5种可能,分别是①1个和尚上船、②2个和尚上船、③1个妖怪上船、④2个妖怪上船,⑤一个和尚与一个妖怪上船。
对上述5种可能逐一进行约束验证,如果验证不通过,则忽略这种可能。
避免陷入"环路":
在搜索状态时,会遇到“状态环路”,例如array(3,3,0,0) -> array(3,2,0,1) -> array(3,3,0,0),有一些状态环路包含的步骤可能会更多一些。如果对这种情况不做处理,搜索就会陷入循环从而无法得出结果。
如何避免“状态环路”:
首先定义状态的数据结构:
$status = array( ‘id‘, ‘value‘, ‘ancestor_ids‘, ‘boat_status‘ );
$status包含4个属性:id、value、ancestor_ids、boat_status,其中,每一个$status的id都是唯一的(id >= 0)(只有初始状态的id = 0),value是一个数组,代表状态的值,例如array(3,3,0,0),ancestor_ids也是一个数组,包含了(本状态的)所有父、祖父、。。。、状态的id,boat_status代表船的位置。
当一个状态和它的ancestor_ids中包含的状态中的任意一个的value和boat_status完全一致时,就可以判定这个状态前面已经出现过了,直接忽略它就可避免“状态环路”。
问题解答:
总共有4种不同的安排可以让和尚和妖怪安全地过河,这4种安排所使用的步骤数目都是一样的,都是11步。
程序设计(PHP):
1 <?php 2 echo ‘<p>问题描述:</p>‘; 3 echo ‘<p>有三个和尚和三个妖怪,他们要利用唯一一条小船过河,这条小船一次最多只能载两个人,同时,无论是在河的两岸还是船上,只要妖怪的数量大于和尚的数量,妖怪们就会将和尚吃掉。现在需要选择一种过河的安排,保证和尚和妖怪都能过河且和尚不能被妖怪吃掉。</p>‘; 4 echo ‘<p>解答:</p>‘; 5 echo ‘<p>其中,[]括号中数字的含义是:第一个代表左岸和尚的数量, 6 第二个代表左岸妖怪的数量,第三个代表右岸和尚的数量,第四个代表右岸妖怪的数量;boat status等于-1代表船在左岸,boat status等于1代表船在右岸。</p>‘; 7 8 echo "<pre>"; 9 10 const left_missionary_num = 0; 11 const left_monster_num = 1; 12 const right_missionary_num = 2; 13 const right_monster_num = 3; 14 15 const boat_missionary_num = 0; 16 const boat_monster_num = 1; 17 18 $currentStatus = array( 19 left_missionary_num => 3, 20 left_monster_num => 3, 21 right_missionary_num=> 0, 22 right_monster_num => 0 23 ); 24 25 $boat = -1;//left bank 26 $statusId = 0; 27 $statuses = array( 28 0 => array( 29 ‘value‘ => $currentStatus, 30 ‘ancestor_ids‘ => array(), 31 ‘boat_status‘ => -1 32 ) 33 ); 34 35 findPath($currentStatus, $statusId, $statusId, $statuses, $boat); 36 37 echo ‘<p>搜索中产生的所有状态(状态树):</p>‘; 38 print_r($statuses); 39 40 41 42 function findPath(array $status, $id, &$statusId, &$statuses, $boat) 43 { 44 $availableBoatStatuses = array( 45 array(boat_missionary_num => 1, boat_monster_num => 0), 46 array(boat_missionary_num => 2, boat_monster_num => 0), 47 array(boat_missionary_num => 0, boat_monster_num => 1), 48 array(boat_missionary_num => 0, boat_monster_num => 2), 49 array(boat_missionary_num => 1, boat_monster_num => 1), 50 ); 51 foreach ($availableBoatStatuses as $availableBoatStatus) { 52 $nextStatus = generateNextStatus($status, $availableBoatStatus, $boat); 53 54 if($nextStatus){ 55 $_boat = -1 * $boat; 56 $ancestorIds = $statuses[$id][‘ancestor_ids‘]; 57 $ancestorIds[] = $id; 58 59 $isAncestor = false; 60 foreach ($ancestorIds as $ancestorId) { 61 if($statuses[$ancestorId][‘value‘] === $nextStatus 62 && $_boat === $statuses[$ancestorId][‘boat_status‘] 63 ){ 64 $isAncestor = true; 65 break; 66 } 67 } 68 69 if($isAncestor){ 70 continue; //Ignore the result if $nextStatus === $statuses[‘$ancestorId‘] 71 } 72 73 $statusId++; 74 75 $statuses[$statusId] = array( 76 ‘value‘ => $nextStatus, 77 ‘ancestor_ids‘ => $ancestorIds, 78 ‘boat_status‘ => $_boat 79 ); 80 81 if($nextStatus === array(0,0,3,3)){ 82 83 foreach ($statuses[$statusId][‘ancestor_ids‘] as $key => $ancestorId) { 84 //print_r($statuses[$ancestorId][‘value‘]); 85 if($key % 5 == 0){ 86 echo ‘<br/>‘; 87 } 88 $v = $statuses[$ancestorId][‘value‘]; 89 printf(‘[%d,%d,%d,%d], boat status = %d -->‘, $v[0],$v[1],$v[2],$v[3],$statuses[$ancestorId][‘boat_status‘]); 90 } 91 printf(‘[%d, %d, %d, %d], boat status = %d‘, $nextStatus[0],$nextStatus[1],$nextStatus[2],$nextStatus[3],$_boat); 92 echo ‘<br/>‘; 93 94 printf(‘总共使用 %d 步完成‘, count($statuses[$statusId][‘ancestor_ids‘])); 95 echo ‘<hr>‘; 96 }else{ 97 findPath($nextStatus, $statusId, $statusId, $statuses, $_boat); 98 } 99 } 100 } 101 } 102 103 /** 104 * @param array $status 105 * @param array $availableBoatStatus 106 * @param int $boatStatus 107 * @return array|bool 108 */ 109 function generateNextStatus(array $status, array $availableBoatStatus, $boatStatus) 110 { 111 if($boatStatus < 0){ 112 //船在左岸,上船后 113 $status[left_missionary_num] = $status[left_missionary_num] - $availableBoatStatus[boat_missionary_num]; 114 $status[left_monster_num] = $status[left_monster_num] - $availableBoatStatus[boat_monster_num]; 115 //船到右岸,下船后 116 $status[right_missionary_num] = $status[right_missionary_num] + $availableBoatStatus[boat_missionary_num]; 117 $status[right_monster_num] = $status[right_monster_num] + $availableBoatStatus[boat_monster_num]; 118 }else{ 119 //船在右岸,上船后 120 $status[right_missionary_num] = $status[right_missionary_num] - $availableBoatStatus[boat_missionary_num]; 121 $status[right_monster_num] = $status[right_monster_num] - $availableBoatStatus[boat_monster_num]; 122 //船到左岸,下船后 123 $status[left_missionary_num] = $status[left_missionary_num] + $availableBoatStatus[boat_missionary_num]; 124 $status[left_monster_num] = $status[left_monster_num] + $availableBoatStatus[boat_monster_num]; 125 } 126 127 foreach ($status as $value) { 128 if($value < 0){ 129 return false; 130 } 131 } 132 133 if(($status[left_missionary_num] >= $status[left_monster_num]) && 134 ($status[right_missionary_num] >= $status[right_monster_num]) 135 || $status[left_missionary_num] === 0 || $status[right_missionary_num] === 0){ 136 return $status; 137 } 138 139 return false; 140 }