#!/usr/bin/perl -w # 演算法: knapsack problem 背包問題, 兩個版本. # 作者: 洪朝貴 http://www.cyut.edu.tw/~ckhung # 授權: public domain (但如果您要將我的程式改寫成有用的大程式, # 強烈建議將您的版本施以 GPL) # 使用方式: 把 $store = ... 修改成你的物品資料, 格式為: # { n=>"物品名稱", v=>價值, w=>重量, c=>可取用個數 }, # 並修改負重限制. 物品名稱可以是任何合法的變數名稱. # (但若超過兩個字母, 印出來會有點醜) 最後下 # perl knapsack # 命令執行程式. # 印出來的結果, 前半部是 "同種物品無限量供應版本"; 後半部是 # "同種物品指定限量供應版本" . # 提示: 試試看將 "每單位重量最有價值" 的物品供應量減少, 更容易了解 # 兩演算法的差異. use strict; sub ks_ul { # 同種物品無限量供應版本 my ($limit, $store) = @_; my ( # 以下是迴圈的最主要註標變數: $item, # 正在處理的物件 $item $load, # 正在處理的負重限制 $load # 以下是動態規劃中的主要表格: $value, # $value->[$i] 記載負重 $i 限制下的最高價值. $last, # $last->[$i] 記載負重限制 $i 下的最佳解, # 所應取的物品當中, 最後取的那一件是何種物品? ); my ($tics) = " "; map { $tics .= $_ % 5 == 0 ? sprintf "-%2d", $_ : "---"; } 1..$limit; print "$tics\n"; @$value = (0) x ($limit+1); for ($item=0; $item<=$#$store; ++$item) { my ($out); # 即將印出來的字串, 暫存於此 my ($t) = $store->[$item]; for ($load=1; $load<=$limit; ++$load) { if ($load >= $t->{w} and $value->[$load] < $value->[$load-$t->{w}] + $t->{v}) { $value->[$load] = $value->[$load-$t->{w}] + $t->{v}; $last->[$load] = $item; } $out->[0] .= sprintf "%3d", $value->[$load]; $out->[1] .= sprintf " %2s", defined $last->[$load] ? $store->[$last->[$load]]->{n} : "-"; } printf "%2s:$out->[0]\n $out->[1]\n", $t->{n}; } print "$tics\n"; $load = $limit; my ($out); while ($load > 0) { my ($t) = $store->[$last->[$load]]; $out->[0] .= sprintf "%3s", $t->{n}; $out->[1] .= sprintf "%3d", $t->{v}; $out->[2] .= sprintf "%3d", $t->{w}; $load -= $t->{w}; } printf " $out->[0]\n%3d |$out->[1]\n%3d |$out->[2]\n\n", $value->[$limit], $limit; } sub ks_count { # 同種物品指定限量供應版本 my ($limit, $store) = @_; my ( # 以下是迴圈的最主要註標變數: $load, # 正在處理的負重限制 $load $item, # 正在處理的物件 $item # 以下是動態規劃中的主要表格: $value, # $value->[$i] 記載負重限制 $i 下的最高價值. $last, # $last->[$i] 記載負重限制 $i 下的最佳解, # 所應取的物品當中, 最後取的那一件是何種物品? ); unshift @$store, { n=>"-", v=>0, w=>1, c=>9999 }; print "val/lim | bi"; for ($item=1; $item<=$#$store; ++$item) { printf " %2s", $store->[$item]{n}; } $value->[0] = 0; for ($load=1; $load<=$limit; ++$load) { my ($best_item, $best_val) = (0, 0); for ($item=1; $item<=$#$store; ++$item) { my ($i) = $store->[$item]; my ($ll) = $load - $i->{w}; # lighter load # 是否可捨棄一些東西, 騰出重量改拿 $item ... next if ($ll < 0); # 價值會更高嗎? my ($new_val) = $value->[$ll] + $i->{v}; next if $new_val <= $best_val; # 還有得拿嗎? next if taken($ll, $item, $last, $store) >= $i->{c}; $best_item = $item; $best_val = $new_val; } $value->[$load] = $best_val; $last->[$load] = $best_item; printf "\n%3d/%3d | %2s", $best_val, $load, $store->[$best_item]{n}; for ($item=1; $item<=$#$store; ++$item) { printf "%3d", taken($load, $item, $last, $store); } } print "\n"; } sub taken { # 負重限制 $load 下的最佳解, $item 類物品取了幾件? my ($load, $item, $last, $store) = @_; my ($n) = 0; while ($load > 0) { my ($t) = $store->[$last->[$load]]; ++$n if $t->{n} eq $store->[$item]{n}; $load -= $t->{w}; } return $n; } my ($store); $store = [ {n=>"A", v=>19, w=> 5, c=> 6}, {n=>"B", v=>24, w=> 6, c=> 6}, {n=>"C", v=>33, w=> 8, c=> 5}, {n=>"D", v=>45, w=> 11, c=> 1}, {n=>"E", v=>50, w=> 12, c=> 1}, ]; # $store = [ # {n=>"a", v=>10, w=> 3, c=> 3}, # {n=>"b", v=>13, w=> 4, c=> 9}, # {n=>"c", v=>17, w=> 5, c=> 3}, # {n=>"d", v=>25, w=> 7, c=> 1}, # {n=>"e", v=>32, w=> 9, c=> 1}, # ]; # $store = [ # {n=>"A", v=> 4, w=> 3, c=> 2}, # {n=>"B", v=> 5, w=> 4, c=> 3}, # {n=>"C", v=>10, w=> 7, c=> 2}, # {n=>"D", v=>11, w=> 8, c=> 5}, # {n=>"E", v=>13, w=> 9, c=> 2}, # {n=>"F", v=>15, w=>10, c=> 1}, # ]; my ($i); print " name : "; foreach $i (@$store) { printf "%3s", $i->{n}; } print "\n"; print " value: "; foreach $i (@$store) { printf "%3d", $i->{v}; } print "\n"; print " wght : "; foreach $i (@$store) { printf "%3d", $i->{w}; } print "\n"; print "(count: "; foreach $i (@$store) { printf "%3d", $i->{c}; } print ")\n"; print "\n"; ks_ul(25, $store); ks_count(45, $store);