トップページに戻る
次のPL/SQLの問題へ
前のPL/SQLの問題へ
Java版
PL/SQL2 ナップサック問題
SQLパズル
ALGORITHM NOTE ナップザック問題 Knapsack Problem
5つの荷物(重量と価値はそれぞれ、(3と4)(4と5)(5と6)(6と7)(9と10) が各1個)がある。
重量の合計が15以内で、最も価値の合計が高くなるような荷物の組合せを見つけよ。
荷物名 重量 価値
------ ---- ----
A 3 4
B 4 5
C 5 6
D 6 7
E 9 10
データ作成スクリプト
create table nimotuT(nimotu,KG,Val) as
select 'a',3, 4 from dual union
select 'b',4, 5 from dual union
select 'c',5, 6 from dual union
select 'd',6, 7 from dual union
select 'e',9,10 from dual;
SQL(全ての組み合わせを列挙)
col nimotuList for a20
select a.nimotuList,sum(b.KG) as sumKG,sum(b.Val) as sumVal
from (select KG,Val,sys_connect_by_path(nimotu,',') as nimotuList,
sys_connect_by_path(RowIDToChar(RowID),',') as RowIDList
from (select KG,Val,nimotu,Row_Number() over(order by KG) as rn
from nimotuT)
connect by prior rn+1 = rn) a,
nimotuT b
where instr(a.RowIDList,RowIDToChar(b.RowID)) > 0
group by a.nimotuList
having sum(b.KG) <=15
order by sumVal desc,sumKG desc,a.nimotuList;
nimotuList sumKG sumVal
---------- ----- ------
,b,c,d 15 18
,d,e 15 17
,a,b,c 12 15
,c,d 11 13
,b,c 9 11
,e 9 10
,a,b 7 9
,d 6 7
,c 5 6
,b 4 5
,a 3 4
PL/SQL(全ての組み合わせを列挙)
declare
sumKG pls_Integer;
sumVal pls_Integer;
willOut Varchar2(100);
begin
for A in 0..1 Loop
for B in 0..1 Loop
for C in 0..1 Loop
for D in 0..1 Loop
for E in 0..1 Loop
sumKG := 0; sumVal :=0;
if A=1 then sumKG:= sumKG+3; sumVal:= sumVal+ 4; end if;
if B=1 then sumKG:= sumKG+4; sumVal:= sumVal+ 5; end if;
if C=1 then sumKG:= sumKG+5; sumVal:= sumVal+ 6; end if;
if D=1 then sumKG:= sumKG+6; sumVal:= sumVal+ 7; end if;
if E=1 then sumKG:= sumKG+9; sumVal:= sumVal+10; end if;
willOut := null;
if sumKG <= 15 and 0 < sumVal then
if A = 1 then willOut := willOut || 'A'; end if;
if B = 1 then willOut := willOut || 'B'; end if;
if C = 1 then willOut := willOut || 'C'; end if;
if D = 1 then willOut := willOut || 'D'; end if;
if E = 1 then willOut := willOut || 'E'; end if;
willOut := willOut || ',sumKG=' || to_char(sumKG);
willOut := willOut || ',sumVal=' || to_char(sumVal);
DBMS_Output.Put_Line(willOut);
end if;
end Loop;
end Loop;
end Loop;
end Loop;
end Loop;
end;
/
PL/SQL(動的計画法)
declare
cursor cur is select nimotu,KG,Val from nimotuT order by nimotu;
type SaveDataDef is table of cur%rowType index by binary_integer;
SaveData SaveDataDef;
--重さ合計の制限
OmosaSumLimit Constant pls_Integer :=15;
--添字が重さの合計、値が価値合計の最大値
type SumValArrDef is table of pls_Integer index by binary_integer;
SumValArr SumValArrDef;
--最後に矢印をセットした品物の名称
type LastArrowItemArrDef is table of char(1) index by binary_integer;
LastArrowItemArr LastArrowItemArrDef;
type LastArrowItemArrLogDef is table of LastArrowItemArrDef index by binary_integer;
LastArrowItemArrLog LastArrowItemArrLogDef;
wkSumOmosa pls_Integer;
wkSumVal pls_Integer;
CanUpdate Boolean;
--動的計画法の現在の状態を表示
procedure PrintJyoutai is
WillOut1 VarChar2(4000);
WillOut2 VarChar2(4000);
WillOut3 VarChar2(4000);
begin
for I in 0..OmosaSumLimit Loop
WillOut1 := WillOut1 || '[' || LPad(to_char(I),2) || ']';
WillOut2 := WillOut2 || case when SumValArr.Exists(I)
then '[' || LPad(to_char(SumValArr(I)),2) || ']'
else '[ ]' end;
WillOut3 := WillOut3 || case when LastArrowItemArr.Exists(I)
then '[' || LPad(to_char(LastArrowItemArr(I)),2) || ']'
else '[ ]' end;
end Loop;
DBMS_Output.Put_Line(WillOut1);
DBMS_Output.Put_Line(WillOut2);
DBMS_Output.Put_Line(WillOut3);
end;
--動的計画法の結果を表示
procedure PrintResult is
MaxSumVal pls_Integer := 0;
MaxSumInd binary_integer := 0;
CurrInd binary_integer := 0;
CurrLog LastArrowItemArrDef;
type SelectedItemListDef is table of char(1) index by binary_integer;
SelectedItemList SelectedItemListDef;
WillOut VarChar2(4000);
begin
--最大の価値を持つ、重さ合計を求める
for I in 0..OmosaSumLimit Loop
if SumValArr.Exists(I) then
if MaxSumVal < SumValArr(I) then
MaxSumVal := SumValArr(I);
MaxSumInd := I;
end if;
end if;
end Loop;
DBMS_Output.Put_Line('重さ合計が' || to_char(OmosaSumLimit) || '以下での最大の価値は、');
DBMS_Output.Put_Line('重さ合計=' || to_char(MaxSumInd)
|| 'で価値合計=' || to_char(MaxSumVal) || 'です。');
DBMS_Output.Put_Line('■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■');
DBMS_Output.Put_Line('選択した荷物を表示します。');
CurrInd := MaxSumInd;
for I in Reverse 0..LastArrowItemArrLog.Count-1 Loop
CurrLog := LastArrowItemArrLog(I);
--使用済の荷物を含むログならContinue
for J in 0..CurrLog.Count-1 Loop
for K in 0..SelectedItemList.Count-1 Loop
if CurrLog.Exists(J) and CurrLog(J) = SelectedItemList(K) then
goto Continue001;
end if;
end Loop;
end Loop;
for J in 1..SaveData.Count Loop
if SaveData(J).nimotu = CurrLog(CurrInd) then
WillOut := 'ID=' || SaveData(J).nimotu || '、';
WillOut := WillOut || '重さ= ' || SaveData(J).KG || '、';
WillOut := WillOut || '価値= ' || SaveData(J).Val;
DBMS_Output.Put_Line(WillOut);
SelectedItemList(SelectedItemList.Count) := SaveData(J).nimotu;
CurrInd := CurrInd - SaveData(J).KG;
exit;
end if;
end Loop;
exit when CurrInd = 0;
<<Continue001>> null;
end Loop;
end;
begin
open cur;
fetch cur bulk collect into SaveData;
close cur;
--初期設定
SumValArr(0) := 0;
for I in 1..SaveData.Count Loop
for J in Reverse 0..OmosaSumLimit Loop
--重さ制限を超えてしまう場合
wkSumOmosa := J+SaveData(I).KG;
if wkSumOmosa > OmosaSumLimit then goto Continue002; end if;
--価値の和を求める
if SumValArr.Exists(J) = false then goto Continue002; end if;
wkSumVal := SumValArr(J) + SaveData(I).Val;
--重さ合計での最大の価値を更新可能かを判定
CanUpdate := false;
if SumValArr.Exists(wkSumOmosa) = false then
CanUpdate := true;
elsif SumValArr(wkSumOmosa) < wkSumVal then
CanUpdate := true;
end if;
if CanUpdate then
SumValArr(wkSumOmosa) := wkSumVal;
LastArrowItemArr(wkSumOmosa) := SaveData(I).nimotu;
end if;
<<Continue002>> null;
end Loop;
LastArrowItemArrLog(LastArrowItemArrLog.Count) := LastArrowItemArr;
DBMS_Output.Put_Line('■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■');
DBMS_Output.Put_Line(SaveData(I).nimotu || 'の処理結果');
PrintJyoutai();
end Loop;
DBMS_Output.Put_Line('■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■');
DBMS_Output.Put_Line('動的計画法の結果を表示します。');
PrintResult();
end;
/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
aの処理結果
[ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]
[ 0][ ][ ][ 4][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ a][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
bの処理結果
[ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]
[ 0][ ][ ][ 4][ 5][ ][ ][ 9][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ a][ b][ ][ ][ b][ ][ ][ ][ ][ ][ ][ ][ ]
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
cの処理結果
[ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]
[ 0][ ][ ][ 4][ 5][ 6][ ][ 9][10][11][ ][ ][15][ ][ ][ ]
[ ][ ][ ][ a][ b][ c][ ][ b][ c][ c][ ][ ][ c][ ][ ][ ]
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
dの処理結果
[ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]
[ 0][ ][ ][ 4][ 5][ 6][ 7][ 9][10][11][12][13][15][16][17][18]
[ ][ ][ ][ a][ b][ c][ d][ b][ c][ c][ d][ d][ c][ d][ d][ d]
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
eの処理結果
[ 0][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]
[ 0][ ][ ][ 4][ 5][ 6][ 7][ 9][10][11][12][13][15][16][17][18]
[ ][ ][ ][ a][ b][ c][ d][ b][ c][ c][ d][ d][ c][ d][ d][ d]
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
動的計画法の結果を表示します。
重さ合計が15以下での最大の価値は、
重さ合計=15で価値合計=18です。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
選択した荷物を表示します。
ID=d、重さ= 6、価値= 7
ID=c、重さ= 5、価値= 6
ID=b、重さ= 4、価値= 5
解説
全ての組み合わせを列挙するよりも、動的計画法で解いたほうが、計算量は、少ないです。