数学建模 - 01背包问题多种解法 | C语言、Matlab、Lingo

别问我一个大学生暑假为什么只放20天,问就是数学建模!好吧其实建模还是挺有意思的,通过学习我掌握了多种计算机语言。前几天的课上讲到了背包问题,只讲了数学上的讲法,没说代码怎么写。当时脑子一热想出了一种特殊的算法,虽然之后的课上讲到了这种想法,但是我还是花了一个下午的时间在讲之前把01背包问题的C语言解法写了出来。讲完后,我今天又把所有典型背包问题的Lingo和Matlab解法完成了。下面讲讲解法...
摘要由CSDN通过智能技术生成

欢迎访问我的博客
别问我一个大学生暑假为什么只放二十几天,问就是数学建模!
好吧其实建模还是挺有意思的,通过学习我掌握了多种计算机语言。前几天的课上讲到了背包问题,只讲了数学上的讲法,没说代码怎么写。当时脑子一热想出了一种特殊的算法,虽然之后的课上讲到了这种想法,但是我还是花了一个下午的时间在讲之前把01背包问题的C语言解法写了出来。讲完后,我今天又把典型背包问题的Lingo和Matlab解法完成了。下面讲讲解法和思路吧!

背包问题

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?(摘自百度百科)
对于背包问题分为以下三类:

一、01背包

01背包即每种物品的数量为1,可以选择放或者不放,这可以转化为二进制中的0和1,而C语言代码对于0和1的枚举是极其方便的,因为他的时间复杂度为O(2^n)。对于常规的背包问题涉及的物品不会特别多,6个以下的问题纯手算都可以轻松解决,而当今的计算机运算速度都非常快,一般15个物品循环6W多次只要0.8秒左右的时间。单纯的枚举不需要考虑很多方面的因素,而在设定了限制条件之后,可以转化成隐枚举的方法缩短运行时间。

对于01背包的C语言思路:

  1. 建立一个item结构体储存每个物品的质量和价值。
  2. 定义一个数组储存倒序的二进制排列数。为什么是倒序的呢?因为十进制数转化为二进制的算法计算出来的二进制是倒序排列的,而倒序恰好也符合了人的思维模式,即从取第一个物品开始(10)而不是最后一个(01)。
  3. 输入各项数据,根据算法的复杂度O(2^n)开始循环,一共产生2^n种排列,对于每种排列计算出该方案的总重量和总价值。
  4. 设置重量条件,符合限制条件后比较总价值与最高价值,保留最大值。

代码如下:

//
//  main.c
//  Knapsack Question
//
//  Created by Kevin on 2019/6/26.
//  Copyright © 2019 ZeKai Jia. All rights reserved.
//

#include <stdio.h>
#include <math.h>

struct Item {
    int Weight;
    int Value;
}item[100];

int main(void) {
    int Boo[40000][16]={0};      			//储存排列,注意空间
    int TotalWeight, TotalValue;
    int Number, Capacity, MaxValue = 0;		//初始化最高价值
    int i, n, k, j = -1;
    printf("Total number of the items:");
    scanf("%d",&Number);
    printf("Input each item's Weight:");
    for ( i=0; i<Number; i++) {
        scanf("%d",&item[i].Weight);
    }
    printf("Input each item's Value:");
    for ( i=0; i<Number; i++) {
        scanf("%d",&item[i].Value);
    }
    printf("Input your backpack's capacity:");
    scanf("%d",&Capacity);
    for ( n=1; n<pow(2, Number); n++) {		//十进制转二进制的算法倒序储存数据
        i = 0;
        k = n;
        while ( k>0 ) {
            Boo[n][i] = k%2;
            i += 1;
            k /= 2;
        }
    }
    k = n;
    for ( n=0; n<k; n++) {
        TotalWeight = 0;
        TotalValue = 0;
        for ( i=0; i<Number; i++) {
            TotalWeight += item[i].Weight * Boo[n][i];
            if ( TotalWeight <= Capacity ) {
                TotalValue += item[i].Value * Boo[n][i];
                if ( TotalValue >= MaxValue) {
                    MaxValue = TotalValue;
                    j = n;
                }
            }
        }
    }
    if ( j == -1 ) {
        printf("No solution!\n");		
    }
    else {
        printf("The roots are:");
        for ( i=0; i<Number; i++) {
            printf("%d ",Boo[j][i]);		//输出最优解
        }
    }
    printf("\nThe max value is:%d\n",MaxValue);
    return 0;
}

大致就是如此,其实这个程序还有缺陷,第一个就是数组储存在栈中,只可以计算16个以下的物品数,当然电脑不同可能有所差异。所以如果想计算足够多的物品,切记要使用 malloc 把数据存储在堆中。
第二个问题就是它仅能处理01问题,对于其他背包问题就无力解决啦,毕竟一旦物品数量设定为k值,复杂度就提升到了O((k+1)^n),恐怕不是超级计算机的话是难以解决这类问题了。所以咱们点到为止,接着下一种背包问题。

补充:

今天我把01背包问题使用 Lingo 进行了求解,由于 Lingo 是专业求解线性和非线性优化问题的软件,属于黑箱模式,我们只需要把题目的条件输入程序就会自动求解,所以代码相当少。唯一的缺点就是对不同题目需要修改代码中的数据,不能像 C 一样读取用户输入的数据进行操作。

代码如下:

model: 
sets:
a/1..7/: Weight, Value, Capacity ;		//设置 n 个同类元素集合
endsets
data :
Weight = 31 10 20 19 4 3 6;
Value = 70 20 39 37 7 5 10;
enddata
max = @sum(a :Value*Capacity) ;	
@sum(a : Weight*Capacity) <= 50 ;		//总重量不超过50的背包
@for(a:@bin(Capacity)) ;
end

End

二、完全背包

在完全背包问题中,所有的物品都有无限次数可用。显而易见的是在这种情况下背包中存放质价比最高的物品最划算,但是最后一件物品并不见得能塞得下这个背包,于是需要挑出这件物品放入质价比较前者低同时质量也较轻的物品。总之,我们要利用乌鸦喝水的思路尽可能多地塞满(不一定能满)背包,并且使总价值最高。

对于完全背包的Matlab思路:
建立动态规划表,例如一个背包容量为5的表格

item weight value 0 1 2 3 4 5
a 2 7 0 3 7 11 14 18
b 1 3 0 3 6 11 14 17
c 3 11 0 0 0 11 11 11

我们令 W i 表示物品质量,V i 表示物品价值

循环阶段:
i = 1 , 2 , 3 i = 1,2,3 i=

  • 11
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值