问题描述:
有三个容积分别为8升、5升、3升的水桶,其中容积为8升的水桶盛满了水,容积为5升和3升的水桶都是空的。三个水桶都没有刻度,现在需要将水桶中的8升水等分成2份,每份都是4升水。条件是只能使用这三个水桶,不能借助其他工具。
问题分析:
对于这个问题,似乎没有“规律”可循,没有专门的算法来求解,因此只能尝试使用“穷举法”来求解。
把每个状态下三个水桶中的水的体积作为status,例如status = array(8,0,0),想要得到的status = array(4,4,0)。
这样,就把问题转化为“寻径”问题了。只需要找到status = array(8,0,0)转化到status = array(4,4,0)的“路径”即可。
对于status有如下限制:
0 <= status[0] <= 8;
0 <= status[1] <= 5;
0 <= status[2] <= 3;
在搜寻sub_status的过程中,要注意sub_status不能和current_status和其祖先相等,要不然程序会进入“环路”。一般的,current_status可以得到好几个sub_status, 如果sub_status和current_status和其祖先的其中一个相等,则忽略这个sub_status。
一共有三个水桶,则可能的“倒水”动作共有6种(3中取2的全排列),但是在不同的status下,这6种倒水动作中有一些是不可用:①从某一个桶中倒水,但是这个桶是空的,②向某一个桶中倒水,但是这个桶已经满了。
程序设计:
按照上面的分析,可以构造一个status类:
status 类有四个protected 字段:
_values 值, 例如array(8,0,0)
_limit 限制, 这里是array(8,5,3)
_children 用来保存本对象的所有sub_status的values
_ancestors 用来保存对象本身的values和祖先的values
程序的大体思路是实例化第一个status——array(8,0,0),在构造函数中搜索sub_status、筛选sub_status,然后实例化所有的sub_status,如果sub_status等于array(4,4,0)则停止实例化,并将_ancestors输出,这样就可以将所有的“路径”找出。
程序示例(php):
面向对象风格
1 php 2 //path: document-root/obj/Status.php 3 4 class Status 5 { 6 protected $_values; 7 protected $_limit; 8 protected $_children; 9 protected $_ancestors; 10 11 public function __construct(array $values, array $limit, array $ancestors) 12 { 13 $this->_values = $values; 14 $this->_limit = $limit; 15 $ancestors[] = $this->_values; 16 $this->_ancestors = $ancestors; 17 18 $this->generateChildren(); 19 } 20 21 protected function generateChildren() 22 { 23 $children = array(); 24 25 for($i=0;$i<count($this->_values);$i++){ 26 for($o=$i+1;$o<count($this->_values);$o++){ 27 $child = $this->turnStatus($i, $o); 28 if($child === array(4,4,0)){ 29 $this->showResult($child); 30 } 31 if($child !== false && !$this->isAncestor($child) && $child !== array(4,4,0)){ 32 $children[] = new Status($child, $this->_limit, $this->_ancestors); 33 } 34 35 $child = $this->turnStatus($o, $i); 36 if($child === array(4,4,0)){ 37 $this->showResult($child); 38 } 39 if($child !== false && !$this->isAncestor($child) && $child !== array(4,4,0)){ 40 $children[] = new Status($child, $this->_limit, $this->_ancestors); 41 } 42 } 43 } 44 $this->_children = $children; 45 } 46 47 protected function isAncestor($value) 48 { 49 foreach ($this->_ancestors as $ancestor) { 50 if($value === $ancestor){ 51 return true; 52 } 53 } 54 return false; 55 } 56 57 protected function turnStatus($i, $o) 58 { 59 $value = $this->_values; 60 if($this->_values[$o] == 0){ 61 return false; 62 } 63 64 if($this->_values[$i] == $this->_limit[$i]){ 65 return false; 66 } 67 if(($this->_limit[$i] - $this->_values[$i]) <= $this->_values[$o]){ 68 $m = $this->_limit[$i] - $this->_values[$i]; 69 }else{ 70 $m = $this->_values[$o]; 71 } 72 73 $value[$o] -= $m; 74 $value[$i] += $m; 75 return $value; 76 } 77 78 protected function showResult(array $child) 79 { 80 $path = $this->_ancestors; 81 $path[] = $child; 82 //print_r($path); 83 Register::append('final_status', $path); 84 //echo "
"; 85 } 86 }
1 php 2 //path: document-root/main.php 3 4 require_once("obj/Status.php"); 5 require_once("Register.php"); 6 echo ''; 7 8 $status = new Status(array(8, 0, 0), array(8, 5, 3), array()); 9 10 11 //var_dump($status); 12 13 $paths = Register::registry('final_status'); 14 printf("共找出 %d 种不同的方式", count($paths)); 15 16 $stepNum = 0; 17 foreach ($paths as $key => $path) { 18 if($key == 0){ 19 $stepNum = count($path); 20 } 21 $_stepNum = count($path); 22 if($_stepNum < $stepNum){ 23 $stepNum = $_stepNum; 24 $_key = $key; 25 } 26 } 27 printf(" 其中,第 %d 种方式所用步数最少,为 %d 步", $_key, $stepNum-1); 28 print_r($paths);
1 php 2 //path: document-root/Register.php 3 4 class Register 5 { 6 static protected $_data = array(); 7 8 9 public function __construct() 10 { 11 } 12 13 static public function register($key, $value) 14 { 15 self::$_data[$key] = $value; 16 } 17 18 static public function append($key, $value) 19 { 20 self::$_data[$key][] = $value; 21 } 22 23 static public function registry($key) 24 { 25 if(isset(self::$_data[$key])){ 26 return self::$_data[$key]; 27 } 28 return null; 29 } 30 }
将上面贴出来的程序按目录放在网站根目录下,访问 main.php 即可得到问题解答。
过程化风格:
1 php 2 //file: document-root/index.php 3 echo ''; 4 5 $limit = array(8,5,3); 6 $current = array( 7 'value' => array(8,0,0), 8 'ancestors' => array( 9 ) 10 ); 11 $records = array(); 12 $tree = getChildren($current, $limit, $records); 13 print_r($records); 14 15 16 /** 17 * @param $current array('value', 'children', 'ancestors') 18 * @param array $limit 19 * @param array $records 20 * @return array 21 */ 22 function getChildren($current, array $limit, array &$records) 23 { 24 $children = array(); 25 $pValue = $current['value']; 26 27 for($i=0;$i<count($pValue);$i++){ 28 for($o=$i+1;$o<count($pValue);$o++){ 29 30 $child = turn($i, $o, $pValue, $limit); 31 if($child !== false && !isAncestor($current, $child)){ 32 $children[] = $child; 33 } 34 35 $child = turn($o, $i, $pValue, $limit); 36 if($child !== false && !isAncestor($current, $child)){ 37 $children[] = $child; 38 } 39 } 40 } 41 42 foreach ($children as $key => $child) { 43 $current['children'][$key]['value'] = $child; 44 45 $ancestors = $current['ancestors']; 46 $ancestors[] = $pValue; 47 $current['children'][$key]['ancestors'] = $ancestors; 48 49 if($child !== array(4,4,0)){ 50 $current['children'][$key]['children'] = getChildren($current['children'][$key], $limit, $records); 51 }else{ 52 $_records = $ancestors; 53 $_records[] = $child; 54 $records[] = $_records; 55 } 56 } 57 return $current; 58 } 59 60 /** 61 * @param $i 62 * @param $o 63 * @param $value array 64 * @param $limit 65 * @return bool|array 66 */ 67 function turn($i, $o, $value, $limit) 68 { 69 if($value[$o] == 0){ 70 return false; 71 } 72 73 if($value[$i] == $limit[$i]){ 74 return false; 75 } 76 if(($limit[$i] - $value[$i]) <= $value[$o]){ 77 $m = $limit[$i] - $value[$i]; 78 }else{ 79 $m = $value[$o]; 80 } 81 82 $value[$o] -= $m; 83 $value[$i] += $m; 84 return $value; 85 } 86 87 function isAncestor(array $current, array $child) 88 { 89 $ancestors = $current['ancestors']; 90 if(in_array($child, $ancestors)){ 91 return true; 92 } 93 return false; 94 }
在浏览器中直接访问这个文件即可。