[简化0-1背包问题]
我们有n件物品,物品i的重量为w[i]。每种物品要么完全放入背包要么不放入背包,问:能否从这n件物品中选择若干件放入背包,使之重量和恰好为s。
我们可以很容易地写出递归解:
bool ans(int s,int n){
if(s == 0) return true;
else if(s<0 || (s>0 && n<1)) return false;
else if(ans(s-w[n-1], n-1)){
cout<<w[n-1]<<" ";
return true;
}
else{
bool tmp=ans(s, n-1);
return tmp;
}
}
下面开始改写为非递归:
step1:定义调用栈帧:
注意到我们递归代码中每一层函数有两个参数,那么栈帧有两个参数:
struct Elem{
int s,n;
int rd // 返回地址(何处调用了这个函数)
// 栈帧构造函数
Elem(int s,int n,int rd):
s(n), n(n), rd(n)
};
step2:将原问题入栈:
bool ans(int s,int n){
// 生成一个栈
stack<Elem> st;
// 创建一个栈帧
Elem x(s,n,0);
// 入栈
st.push(x);
// 储存最近一次调用结果
bool ret;
...
}
step3:程序分析:
有n个递归调用就有n+1个递归出口,将原函数划分为n+1个区域
三个箭头即为三个出口,三个方框代表三个出口将函数划分的三个区域
step4:划分标签并翻译:
将三个区域分别编号并翻译:
有n个递归调用就有n+2个标签
bool ans(int s,int n){
stack<Elem> st;
Elem x(s,n,0);
st.push(x);
bool ret;
L0:
s=st.top().s; n=st.top().n;
if(s==0){
ret=true;
TODO return;
}
if((s<0) || (s>0 && n<1)){
ret=false;
TODO return;
}
// 第一次递归调用
L1:
s=st.top().s; n=st.top().n;
if(ret){
cout<<w[n-1];
ret=true;
TODO return;
}else{
// 第二次递归调用
L2:
TODO return;
L3:
// 递归退出,决定返回什么地址
...
}
step5:goto实现递归调用:
rd=1与rd=2代表不同的调用出口(递归出口)
step6:return全部到最后一个标签并实现递归退出:
bool ans(int s,int n){
stack<Elem> st;
Elem x(s,n,0);
st.push(x);
bool ret;
L0:
s=st.top().s; n=st.top().n;
if(s==0){
ret=true;
goto L3;
}
if((s<0) || (s>0 && n<1)){
ret=false;
goto L3;
}
// 第一次递归调用
st.push((s-w[n-1], n-1, 1));
goto L3;
L1:
s=st.top().s; n=st.top().n;
if(ret){
cout<<w[n-1];
ret=true;
goto L3;
}
else{
// 第二次递归调用
st.push((s, n-1, 2))
goto L0;
}
L2:
goto L3;
L3:
// 递归退出,决定返回什么地址
switch((x=st.top().rd)){
case 0: st.pop(); return ret;
case 1: st.pop(); goto L1;
case 2: st.pop(); goto L2;
default: assert(false);
}
}
来源:https://www.icourse163.org/learn/PKU-1002534001?tid=1470937462#/learn/announce