动态规划 dp00 01背包问题及其扩展 c代码

这几天在学习算法方面动态规划的内容,主要是为了面试。最早接触动态规划是在大学的算法课,当时看书遇到的第一道难题就是01背包问题,自己琢磨了半天也没想出一个好的方法,看课本几行代码就搞定了,感觉特别不可思议,当时觉得算法相当强大,不过也没太花时间在这上边,最近准备大厂面试的时候,发现算法和数据结构的要求相当高,所以开始复习下算法。私以为,卓越的程序员是需要懂得算法的。在阅读内核源码的时候,经常会碰到算法内容,比如内核里面的红黑树,路由表的Trie树等,如果算法基础不够强大,阅读代码都会相当困难,给工作带来极大困扰。算法还有一个特点就是效率特别高,通常是指数级别。比如说,如果用枚举法解决01背包问题,时间复杂度是O(2^n),如果使用动态规划的话,时间复杂度在O(n^2),这样说可以还不明显,我们看下随着n的增长,O(2^n)/O(n^2)的变化图:

差距肉眼可见,随着n的增长,枚举法的时间复杂度几乎是动态规划的指数倍。假设程序员A使用枚举法解决问题,程序员B使用动态规划解决问题,如果你是老板,你愿意招谁呢?记得有谁说过,一个优秀的程序员足以顶上N个普通的程序员,在软件开发领域,还是相当有道理的。闲话不多说,我们看下动态规划。

动态规划(Dynamic Programming)是运筹学的一个分支,是求解决策过程最优化的教学方法。早在20世纪50年代,美国数学家贝尔曼(Rechard Bellman)等人在研究多阶段决策过程的优化问题时,提出了著名的最优性原理,把多阶段决策过程转化为一系列单阶段问题逐个求解,创立了解决多阶段过程优化问题的新方法----动态规划。

动态规划问世以来,在经济管理、生产调度、工程技术等多阶段决策问题的最优控制方面得到了广泛的应用。随着最优化应用的不断深入,动态规划技术越来越成为解决许多重要问题的关键技术。

 看下01背包问题:

已知6种物品和1个可载重量为60的背包,物品i(i=1,2,,6)的重量分别为wi(15,17,20,12,9,14),
产生的效益分别为pi(32,37,46,26,21,30)。在装包时,每一件物品可以装入,也可以不装入,
但不可以拆开。试确定如何装包,使得装包的总收益最大。

从最近的刷题经验来看,解决动态规划类问题需要分为以下几个步骤:

1. 分阶段决策。

2. 确定状态迁移方程(这个过程主要使用递推)

3. 求出最优解

这道题显然可以将一个物品装包作为一个阶段。状态就是一个阶段过后的结果,这里装完物品后的状态包括当前包的总效益以及包剩余的容量,使用M[i][j]来表示总收益,i表示第i个物品,j表示剩余的容量,当j > wi的时候,这时候包里容量可以放得下,这时候有两种可能,一种是装包,收益为(m[i-1][j-wi) + pi,一种是不装包,总收益为m[i-1][j],是否装包需要比较两种收益的大小,取最大的。知道了状态迁移方程,这时候还需要确定边界情况,即M[1][j],比较j和w1的大小即可。

知道了边界和状态迁移方程,所求的最优解即时M[6][60],即总收益。这时候除了得到最大收益,还需要求得最优解,即具体装入了哪个物品,这个可以根据状态迁移方程来得到,比如说如果m[6][60] == m[5][60],说明第六个物品没有装包,否则即装包,以此类推,依次求得其它物品是否装包,从而得到最优解。

动态规划的过程比较简洁,按照步骤顺序求解即可,通常难度在第二步,状态迁移方程的建立,这一点一方面需要仔细分析题目,另一方面就是多加练习找感觉了O(∩_∩)O。

二维约束01背包问题是01背包问题的扩展,01背包问题的限制条件是包的载重量,二维约束01背包问题除了包的载重量问题还有包的容积问题,其实如果懂得了01背包问题,这个扩展就轻而易举了,无非是把m[i][j]变成m[i][j][k],此外,在进行装包判定的时候,除了比较包的剩余载重量,还需要比较包的剩余容积。其它的和普通的01背包没有区别。

下面是01背包问题的c代码:

//01背包问题

#include <stdio.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))

void main()
{
	int i, j, k, n, c, w[30] = {0}, p[30] = {0}, m[30][100] = {0};

	printf("输入物品个数:");	scanf("%d", &n);
	printf("输入背包载重量:");	scanf("%d", &c);
	if (n > 30) n = 30;

	for (i = 0; i < n; i++)
	{
		printf("输入物品%d的重量和效益:",i+1);
		scanf("%d %d", &w[i], &p[i]);
	}

	//初始化边界
	for (i = 0; i <= c; i++)
	{
		if (i >= w[0])
			m[0][i] = p[i];
		else
			m[0][i] = 0;
	}

	//状态递推
	for (i = 1; i < n; i++)
	{
		for (j = 0; j <= c; j++)
		{
			if (j >= w[i])
				m[i][j] = MAX (m[i-1][j], m[i-1][j-w[i]] + p[i]);
			else
				m[i][j] = m[i-1][j];
		}
	}

	//打印最大效益
	printf("最大装包效益为:%d\n", m[n-1][c]);
	printf("背包所装物品为:");
	//打印最优解
	for (j = c, i = n - 1; i > 0; i--)
	{
		if (j >= w[i]){
			if (m[i][j] != m[i-1][j])
			{
				printf("%d ", i+1);
				j -= w[i];
			}
		}
	}
	if (j >= w[i])
		printf("%d \n", i+1);
	printf("\n");	
	return;	
}

参考资料:

1. 数据结构 : C语言版/ 严蔚敏,吴伟民编著

=============================================================================================

Linux应用程序、内核、驱动开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值