背包算法一般采用贪心算法求近似解,但是在做作业的时候常常遇到只要求实现,所以给定特地度量为1的背包算法
这样的情况常常采用的是动态规划实现
动态规划的关键是求解的过程中子问题的解是不独立的,可以这样理解,即他们的解是相关联的,求f(n)的 时候必然
会用到f(n-1)的解,以此类推。所以,才用一组内存存放前N-1项的解是必然的。
一般情况处理一个问题的首先想到代码实现是递归,其次动态规划。下面采用一般方法将递归代码转为动态规划。
举个简单例子:
斐波那契数列实现
static final int maxn=20000;
static long knownf[]=new long[maxn];
static long func(int i) //递归实现
{
if(i==0||i==1)
return 1;
else
return func(i-1)+func(i-2);
}
递归的代码非常简单,但是这样求解F(N)是不合理的,重复计算子问题,极易使栈溢出
下面是动态规划实现
static long f(int i)
{
if(i==0||i==1) return knownf[i]=1;
else
{
long t=0;
if(knownf[i-1]!=0) return knownf[i]=knownf[i-1]+knownf[i-2];
else
{
t=f(i-1)+f(i-2);
System.out.println("f("+i+")="+"f("+(i-1)+")+"+"f("+(i-2)+")");
return knownf[i]=t;
}
}
}
容易发现这个算法用一个数组存储了F[1]-F[N]的一组解,函数结构仍是自顶向下,但是求解过程是线性的
首先查看F[i-1](即第i-1的解)是否已求,将其结果输出;否则,递归求解f(i-1)+f(i-2);形式和递归一样
只是求解后返回一个解填写数组
同样,下面是背包算法的求解:
class item{
public int size;
public int value;
public item(int size,int value)
{
this.size=size;
this.value=value;
}
}
public class Test5_12 {
static item[] items=new item[5];
public static void init()
{
items[0]=new item(1,2);
items[1]=new item(2,2);
items[2]=new item(4,3);
items[3]=new item(3,5);
items[4]=new item(6,9);
}
//递归出口space<0
public static int knap(int cap)
{ // cap is bag size
int i,space,max,t;
for( i=0,max=0;i<5;i++)
if((space=cap-items[i].size)>=0)//向下搜索返回cap=space最大解
if((t=knap(space)+items[i].value)>max)//搜索到叶节点时候返回max
max=t;
return max;
}
public static void main(String[] args) {
// TODO Auto-generated method stubinit();
System.out.println(knap(6));
}
}
上面是递归求解knap,值得注意的是函数public static int knap(int cap);改为
public static int knap(int cap,int k) k作为未访问的物品序列,此时即为0-1背包算法
下面改为动态规划实现:
static int knap(int M)
{
int i,space,max,maxi=0,t;
if(maxKnown[M]!=unknow)
return maxKnown[M];
for(i=0,max=0;i<N;i++)
if((space=M-items[i].size)>=0)
if((t=knap(space)+items[i].val)>max)
{
max=t;
maxi=i;
}
maxKnown[M]=max;itemKnown[M]=items[maxi];
return max;
}
上面形式和递归实现很像,与上一个例子不同的是,背包问题填的是一张二维数组,斐波那契数列填的是一个一维数组