0-1背包问题的多种算法设计与分析

0-1背包问题的多种算法设计与分析

0-1背包问题描述:给定一组共n个物品,每种物品都有自己的重量wi(i=1~n)和价值vi(i=1~n),在限定的总重量(背包的容量C)内,如何选择才能使得选择物品的总价值之和最高。选择最优的物品子集放置于给定背包中,最优子集对应n元解向量(x1,…xn), xi∈{0或1},因此命名为0-1背包问题。

输入:为方便这种大规模输入数据的调试,采用文件输入,标准输出(文件输出当然也可)的形式。数据输入的格式如下:每组测试数据包含n+1行,第1行为C和n,表示背包容量为C且有n个物品,接下来n行是这n个物品的重量wi和价值vi。背包容量和物品重量都为整数。

输出:输出n+1行。第1行为所选物品的最大价值之和,接下来n行为装入背包的物品所对应的n元最优解向量(x1,…xn), xi∈{0或1},但每行以"i  xi"形式输出。

任务:选用三种算法解决问题。

2. 多种算法详细设计

1) 递归

伪代码:

//递归函数

f(int i)

{

    if(i >= n){ if(cp > best) best = cp; }

    if(c - cw > w[i]){ cw += w[i];cp += v[i];f(i+1); cw -= w[i];cp -= v[i];f(i+1);    }

    else f(i+1);

}

//主函数

main()

{   

//调用freopen函数,采用文件输入

    freopen("input.txt","r",stdin);

    freopen("output.txt","w",stdout);

    scanf( 背包最大重量与物品个数 );

    for(i=0;i从文件中逐个读取数值

start = clock(); //调用clock函数计算算法开始时间

f(0);   //递归入口

    printf( 最大物品价值 );

    printf( 物品存放状态 );

    finish = clock(); //调用clock函数计算算法结束时间

    printf( 算法使用时间 );

}

数据类型的定义:

#define SIZE 整数

clock_t start, finish; //算法开始与结束的时间变量

int cw=0, cp=0; //cw是递归到当前的物品重量,cp是递归到当前的物品价

int s; //获得的最大价值

int w[SIZE]; //每个物品的重量

int v[SIZE]; //每个物品的价值

int x[SIZE]; //物品存放状态

int n; //物品个数

int C; //背包最大容量

主程序与模块之间的层次(调用)关系:

main() 先调用 f()f() 再递归调用自身,返回最大价值,保存最优解

算法简略分析:

对于背包容量为n件物品的状态,记为[c, n],根据是否将第i件物品放入背包。若放入背包可以划分成两种状态,则背包状态变为[c-w[i]n-1]。若不放入背包,则状态变为[cn-1]。一直到容量c等于0或到第1件物品。然后对比两种状态,取较大的结果返回。期间用 x[n] 记录第n件物品是否放入背包,用以求出最优解向量。

2) 动态规划

伪代码:

 

//比较函数

int max(int a,int b)

{

   if(a>=b) return a;

   else  return b;

}

//动态规划处理背包问题

int KnapSack(int n,int w[],int v[],int x[],int c)

{

//动态规划中的分治体现在用两个循环对物品的个数和背包最大重量进行划分,求 解子问题

    //当物品为1个时,最大重量从0C变化,求得其中最大价值;当物品为2个时, 最大重量从0C变化,求得其中最大价值;当物品为3个时……

    for(i=0;i<=n-1;i++)

{

        for(j=0;j<=c;j++) if(j

             else V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]); //j-w[i]保证总重量不超过C

    }

    j=c;

    for(i=n-1;i>=0;i--)

    {

        if(V[i][j]>V[i-1][j]){ x[i]=1; j=j-w[i]; }

        else x[i]=0;

    }

    return V[n-1][c];

}

//主函数

main()

{

//调用freopen函数,采用文件输入

    freopen("input.txt","r",stdin);

    freopen("output.txt","w",stdout);

    scanf( 背包最大重量与物品个数 );

    for(i=0;i从文件中逐个读取数值

start = clock(); //调用clock函数计算算法开始时间

    s=KnapSack(n,w,v,x,c); //动态规划入口

    printf( 最大物品价值 );

    printf( 物品存放状态 );

    finish = clock(); //调用clock函数计算算法结束时间

    printf( 算法使用时间 );

}

数据类型的定义:

#define SIZE 整数

#define WEIGHT 整数

clock_t start, finish; //算法开始与结束的时间变量

int V[SIZE][WEIGHT] = {0}; //i个物品装入容量为j的背包中获得的最大价值, 初始化为0

int s; //获得的最大价值

int w[SIZE]; //每个物品的重量

int v[SIZE]; //每个物品的价值

int x[SIZE]; //物品存放状态

int n; //物品个数

int c; //背包最大容量

主程序与模块之间的层次(调用)关系:

main() 调用 KnapSack() ,返回最大价值,保存最优解;

KnapSack() 在比较的时候调用 max(),返回较大值。

算法简略分析:

V(i,0)=V(0,j)=0

V(i,j)=V(i-1,j)  j

  V(i,j)=max{V(i-1,j) ,V(i-1,j-wi)+vi) }  j>wi

①式表明:如果第i个物品的重量大于背包的容量,则装人前i个物品得到的最大价值和装入前i-1个物品得到的最大价是相同的,即物品i不能装入背包;②式表明:如果第i个物品的重量小于背包的容量,则会有一下两种情况:

a.如果把第i个物品装入背包,则背包物品的价值等于第i-1个物品装入容量位j-wi 的背包中的价值加上第i个物品的价值vi;

b.如果第i个物品没有装入背包,则背包中物品价值就等于把前i-1个物品装入容量为j的背包中所取得的价值。

显然,取二者中价值最大的作为把前i个物品装入容量为j的背包中的最优解。

3) 贪心算法

伪代码:

//交换两个浮点数

void swapD(double *vlo, double *vhi)

{

    double temp;

    temp = *vlo; *vlo = *vhi; *vhi = temp;

}

//交换两个整数

void swapI(int *vlo, int * vhi)

{

    int temp;

    temp = *vlo; *vlo = *vhi; *vhi = temp;

}

 

//起泡排序

int bubble(int lo, int hi)

{

    int sorted = 1;

    while(++lo < hi)

        if(d[lo] > d[hi])

        {

            sorted = 0;

            swapD(&d[lo], &d[hi]);

            swapI(&v[lo], &v[hi]);

            swapI(&w[lo], &w[hi]);

        }

    return sorted;

}

//主函数

int main()

{

//调用freopen函数,采用文件输入

    freopen("input.txt","r",stdin);

    freopen("output.txt","w",stdout);

    scanf( 背包最大重量与物品个数 );

    for(i=0;i

    {

        scanf("%d%d",&w[i],&v[i]); //从文件中逐个读取数值

        d[i] = 1.0*v[i]/w[i]; //将每个物品的价值与重量作比值

    }

    start = clock(); //调用clock函数计算算法开始时间

    int k = n; while(!bubble(-1, k--)); //起泡排序入口

//求最大价值或近似解

    for(i = n-1; i >= 0; i--)

{

if(c-w[i] >= 0){c -= w[i];s += v[i];}

}

    printf( 最大物品价值 );

    printf( 物品存放状态 );

    finish = clock(); //调用clock函数计算算法结束时间

    printf( 算法使用时间 );

 

}

数据类型的定义:

clock_t start, finish; //计算时间的变量

double d[10000]; //存储每个物品的价值与重量的比值

int s; //获得的最大价值

int w[SIZE]; //每个物品的重量

int v[SIZE]; //每个物品的价值

int x[SIZE]; //物品存放状态

int n; //物品个数

int c; //背包最大容量

主程序与模块之间的层次(调用)关系:

main() 调用 bubble() 进行排序,返回排好序的d[]

bubble() 调用 swapD() 交换浮点数,调用 swap() 交换整数。

算法简略分析:

思想比较简单,从i件物品中挑出价值密度最大的一件,判断是否能装入背包,能的话就装入,然后从剩下的物品中找出价值密度最大的一件判断,依此类推直到最后一件物品。因为此算法并没有从整体考虑问题,即有可能到最后由于背包剩余的空间较多,所以有其他背包利用率较高的解总价值更高,因此此算法的出的解是近似解,并不是最优解,但考虑到算法执行效率,效果相对来说比动态规划要好一些。

 

3. 多种算法调试和测试

1) 调试过程中遇到的问题是如何解决的?

问题中大多数都是自己的粗心导致结果不正确,还有对算法了解不够透彻,但经过一番仔细的纠正之后都通过了测试用例。在求解向量的过程参考了书上的求解过程,知道通过辅助储存用的数组m来求得解向量,便解决了问题。个别比较烦人的问题就请教了同学,借鉴了他们的思路,让我们更快地解决了bug

2) 算法的时空分析和是否有改进的设想?

递归:因为对于每个物品都有2种状态需要判断,所以时间复杂度应该为O2^n)。因为不需要辅助数组,所以空间要求不高。

动态规划:动态规划是在递归基础上增加了辅助数组的算法,具体的时间复杂度很难计算,不过从测试的结果来看,要比递归快不少。但是相应的增加大小为n*c的数组,考虑到数据较大时c可能会很大,所以空间消耗还是不少的。

贪心算法:因为是通过对比物品的价值密度来计算的,相当于是对物品的价值密度来进行排序,所以时间复杂度为On^2)。空间上因为用了一个数字来储存物品价值密度相关信息,为3*n,相对于动态规划来说节省了很多空间。

3) 测试用例选择是否得当?

本实验所用的测试数据参考了老师所给的测试用例,有5组数据,数据量分别为5010020050010005000

4) 调试的经验和体会。

 因为实验的数据比较多,所以调试时要仔细,很多时候都是因为几个小的粗心导致结果错误,为此需要付出较多的时间跟精力来找出这些错误,结果是很得不偿失的。所以应该在打代码阶段就仔细的看代码,保证没有细节上的错误。排除了粗心导致的错误,如果是方法上的错误,个人感觉应该参考书上或网络上别人写的代码,其他人写的好的地方可以借鉴一下,并以此修改自己的代码,使算法更加完善。

4. 多种算法对比

从运行时间、寻找是否为最优解、能够求解的问题规模等方面进行列表对比和分析:

 

 

 

算法         

数据(n

50

100

200

500

1000

5000

结果

递归

120s

 

 

 

 

 

 

 

 

 

 

最优解

动态规划

<1ms

5ms

16ms

100ms

500ms

16s

最优解

贪心算法

<1ms

<1ms

<1ms

2ms

3ms

60ms

近似解

1

从表1中看出从一开始递归的效率就非常差,而动态与贪心在小量数据中效率并无多大差别,在大量数据贪心算法有着无与伦比的效率。

5. 多种算法实现清单

见文件夹中源程序

你可能感兴趣的:(算法,01背包问题)