具体可参考《算法图解》秕9章,原书中给出了思路,但没给代码。
书中使用了填充表格的方法,具体我就不说了,书上说得比我好。
注意书中说的那个公式,在我写的代码中,核心方法是:fillTable(),就是用来实现这个公式的
这一个例题,也是背包问题,后面的代码中,我也把这个例题输入了
PHP代表实现如下:
class BackPack{
protected $capacity;//定义背包容量
protected $unit=1;//背包最小单位(用于处理比如背包最小为0.5格时)
protected $unitTimes=1;//从最小单位转换成1的系数(就是 1除以 $unit)
protected $goodsList=[];
protected $table=[];
public function __construct($capacity,$goodsList,$unit=1)
{
$this->capacity=$capacity;
$this->goodsList=$goodsList;
$this->unit=$unit;
if($this->unit!=1){
$this->packageUnit();
}
}
//重点和难点
protected function fillTable()
{
//填充一个二维表格,每一行是一个商品。每一列是动态规划的背包容量
foreach ($this->goodsList as $i=>$goods) {
for ($j = 1; $j <= $this->capacity; $j++) {
//获取同一列中,上一行单元格的值
$previousCellCost= $this->table[$i-1][$j]['cost'] ?? 0;
//计算将当前商品放入后的剩余空间
$freeSpace=$j-$goods['weight'];
if($freeSpace<0){//代表装不下,那么就直接把上个单元格的内容,复制到本格
$cell=$this->table[$i-1][$j];
}else{//装得下的情况
if ($freeSpace==0){//如果刚好装下,那就没剩余空间了,所以剩余空间不会对应任何单元格
$freeCell=false;
}else{//能装下,且有剩余空间,找出剩余空间所对应的单元格
$freeCell=$this->table[$i-1][$freeSpace];
}
$freeCost=$freeCell['cost'] ?? 0;
//当前商品价值+剩余空间价值
$nowCost=$goods['price']+$freeCost;
if($nowCost<=$previousCellCost){
$cell=$this->table[$i-1][$j];//复制上一格
}else{//要更新这一格了
$cell=[
'goodsNames'=>array_merge([$goods['name']],$freeCell['goodsNames'] ?? []),
'cost'=>$nowCost
];
}
}
$this->table[$i][$j]=$cell;
}
}
}
//通过商品名,获取商品
protected function getGoodsByName($name){
foreach ($this->goodsList as $goods) {
if($goods['name']==$name){
return $goods;
}
}
return false;
}
//在表格填充好后,将最后一格取出(如果有值的话)
protected function bestPlan(){
$table=array_reverse($this->table);
foreach ($table as $row) {
$row=array_reverse($row);
foreach ($row as $cell) {
if(!empty($cell['cost'])){
return $cell;
}
}
}
}
//输出结果
protected function showResult(){
$bestCell=$this->bestPlan();
$goodsStr='';
foreach ($bestCell['goodsNames'] as $name) {
$goods=$this->getGoodsByName($name);
$weight=$goods['weight']/$this->unitTimes;//输出结果时,还原为原来的单位
$goodsStr.="{$name}(重量:{$weight},价值:{$goods['price']}) ";
}
$capacity=$this->capacity/$this->unitTimes;//输出结果时,还原为原来的单位
echo "背包大小为{$capacity},装以下物品为最优解".PHP_EOL;
echo $goodsStr . PHP_EOL;
echo '合计价值:' . $bestCell['cost'] . PHP_EOL;
}
//如果背包的单位不是1,将它转换成1
protected function packageUnit(){
if($this->unit==1){
return;
}
$this->unitTimes=1/$this->unit;
$this->capacity=$this->capacity*$this->unitTimes;
foreach ($this->goodsList as &$v) {
$v['weight']=$v['weight']*$this->unitTimes;
}
}
public function run()
{
$this->fillTable();
// print_r(($this->table));
$this->showResult();
}
}
$goodsList=[//商品列表
[
'name'=>'音响',
'price'=>7000,
'weight'=>4
],
[
'name'=>'吉它',
'price'=>1500,
'weight'=>4
],
[
'name'=>'笔记本电脑',
'price'=>4500,
'weight'=>3
],
[
'name'=>'iphone',
'price'=>5000,
'weight'=>2
],
[
'name'=>'双人床',
'price'=>9000,
'weight'=>20
],
[
'name'=>'金项链',
'price'=>1800,
'weight'=>1
],
[
'name'=>'iPad',
'price'=>3000,
'weight'=>3
],
[
'name'=>'智能手表',
'price'=>2500,
'weight'=>1
],
];
//旅游目的地
$tripList=[
[
'name'=>'威斯敏斯特教堂',
'price'=>7,
'weight'=>0.5
],
[
'name'=>'环球剧场',
'price'=>6,
'weight'=>0.5
],
[
'name'=>'英国国家美术馆',
'price'=>9,
'weight'=>1
],
[
'name'=>'大英博物馆',
'price'=>9,
'weight'=>2
],
[
'name'=>'圣保罗大教堂',
'price'=>8,
'weight'=>0.5
],
];
$obj=new BackPack(6,$goodsList);
$obj->run();
$obj=new BackPack(2,$tripList,0.5);
$obj->run();