背包问题-如何在背包容量有限的情况下,装下价值最多的商品 PHP实现

具体可参考《算法图解》秕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();

结果输出:

背包大小为6,装以下物品为最优解

iphone(重量:2,价值:5000)  音响(重量:4,价值:7000)  

合计价值:12000

背包大小为2,装以下物品为最优解

圣保罗大教堂(重量:0.5,价值:8)  英国国家美术馆(重量:1,价值:9)  威斯敏斯特教堂(重量:0.5,价值:7)  

合计价值:24

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值