贪心算法和动态规划的思路及其Python实现

贪心算法
百度的定义: 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

通俗一点讲,当要解决某一个问题时,先判断第一步的最优解,然后把剩下的步骤看作下一个递归的具体问题。

例如0-1背包问题:给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

假设具体问题数值:A物品,重量为6kg,价值为8元,

                         B物品,重量为8kg,价值为13元,

                         C物品,重量为10kg,价值为15元

背包可以装为50kg的物品。

有经验的小朋友肯定首先判断拿取哪一个物品既轻又有价值。A物品:单位重量的价值为8/6(元)

                                                                                             B物品:单位重量的价值为13/8(元)

                                                                                             C物品:单位重量的价值为15/10(元)

计算看出,只要能装,首先要拿B物品,因为单位价值最高。

即剩余50kg

第一步:我有 50 kg背包,我可以选择的物品有 A,B,C

第二步:选择当前这一步最优解,即 B。而问题的背包变成50-8=42kg,而问题只有背包重量改变,重新回到第一步判断。

所以就是:(选择优先B,然后C,然后A)

扫描二维码关注公众号,回复: 5862826 查看本文章

1:我有 50kg 背包,我可以选择A,B,C,总价值为0 ,我选择B

2:我有 42kg 背包,我可以选择A,B,C,总价值为13 ,我选择B

3:我有 34kg 背包,我可以选择A,B,C,总价值为26 ,我选择B

4:我有 26kg 背包,我可以选择A,B,C,总价值为39 ,我选择B

5:我有 18kg 背包,我可以选择A,B,C,总价值为52 ,我选择B

6:我有 10kg 背包,我可以选择A,B,C,总价值为65 ,我选择B
7:我有 2kg 背包,我可以选择 ,总价值为78 ,没有空间选了

这时候有眼尖的小朋友就问了:“那这道题这样做的话,只能取6个B物品,重量为48kg,总价值为6*13=78元。那如果我取5个B物品,1个C物品,不是刚好50kg吗?这样总价值有5*13+15=80元呢”。其实这就看出,贪心算法得到的并不是最优解。

如何用代码实现呢?

coding=utf-8

if name == ‘main‘:
beg = 50 #背包50kg
value = 0 #已经获得的价值
choice = []
while beg > 0: #如果背包还有空位,则递归
if beg >= 8: #选择当前这一步的最优解,既选择B商品
beg = beg - 8
value = value + 13
choice.append(“B”)
elif beg >= 10: #要是B商品选择不了,则选择第二单位价值的物品,即A物品
beg = beg - 10
value = value + 15
choice.append(“A”)
elif beg >= 6:
beg = beg - 6
value = value + 8
choice.append(“C”)
else: #当所有的物品都选择不了,则退出
break
print “剩余的背包重量:”,beg
print “获得的总价值:”,value
print “选择的物品的类型及顺序:”,choice
贪心算法只能保证次优解。

动态规划
动态规划可以看一下http://www.cnblogs.com/sdjl/articles/1274312.html提及到的金矿例题,我觉得算是我见过讲得最简练的博客了(膜拜..)
里面主要提到动态规划的6个思考点:(以下是借鉴上述博客作者的思路,再加以延伸)
1:最优子结构
好像贪心一样,动态规划也是把一个复杂问题分成一个一个步骤,而与贪心不一样的是,贪心是保证当前这一步是当前问题的最优解,而不保证这一步属于整个复杂问题的最优解。而最优子结构就是要规定当前这一步是整个复杂问题的最优解。(区别可参考一开始那个背包问题,最后一步究竟取B,还是取A。)
2:子问题重叠
子问题重叠在贪心也需要体现,走每一步面对的问题都是同一个类型的,例如背包问题的子问题就是:当前我的背包有Xkg空间,我能选择XXX。例如在金矿问题的子问题就是:当前我有X人,XXX剩余金矿。
3:边界
边界体现在背包没有空间,或者金矿没有足够人数取挖。

4:子问题独立
子问题即上面走的每一步之间没有干扰(互不影响)


看完这里还是迷迷糊糊?不急,还没说完,上面那四个点只是基础,还需要剔除重复的步骤。(相当于深搜的剪枝吧..)

这里我再引入一道题:斐波那契数列之青蛙跳台阶。

一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法。

这道题其实用动态规划可以解决。一般的想法:

coding=utf-8

def fib(n): #当前有N个台阶,可以选择跳一个台阶,也可以选择跳两个
if n <=1 : #边界问题,要是当前只剩下一个台阶,则只剩下一个方法跳。
return 1
else: #跳一个台阶和跳两个台阶都是一个选项。
return fib(n-1)+fib(n-2)
print fib(5)
这种最简单的方法是可以实现少台阶的情况,一旦多情况(100个台阶)就运行不下去。http://blog.csdn.net/baidu_28312631/article/details/47418773博客作者阐述了如果动态规划不优化的话,答案可能性有多少。在这里我用图列出一共有多少种可能性。

每到叶结点的0代表剩下0步,确定唯一的跳台阶的唯一确定方案。上面一共有8个叶结点,即有8种方案。不过这里我们不是讨论这道题的解,而是讨论动态规划的优化。

当我剩下1层台阶时,我只有两种可能,就是这里(或者从2到0)

咦,那当我只剩下2层台阶时,我也只有一种可能性,就是

当我剩下3层台阶时,也只有

剩下4层台阶时,有

这样的话,其实我有5层台阶时,其中包括1个(4层台阶子树),2个(3层台阶子树),3个(2层台阶子树),5个(从1到0),4个(从2直接到0)。
这样我用一个“备忘录”(动态规划的一个专用名词)记录我计算过的树结构的话,5层台阶的备忘录就只有5项,而不优化的话,我就要计算答案那么多的可能性(8种),一旦台阶数变多,答案的可能性个数会飞速上升。
经过优化的跳台阶的动态规划:(网上有个版本是用装饰器在未优化的基础上添加功能,我这里简单用普通方法写一遍..)
代码如下:
class Dp1(object): #动态规划类
def init(self,n): #初始化
self.mark = [0 for _ in xrange(n+1)] #定义一个一维数组,初始化全部为0,长度为台阶数。用来当作“备忘录”。
print self.dp(n) #开始递归
def dp(self,n): #递归的方法
self.m = 0 #m的含义是当前n个台阶有m种跳法
if self.mark[n] != 0: #先从备忘录寻找n,若存在mark[n]不等于0,则代表曾经计算过,n个台阶有mark[n]种跳法
self.m = self.mark[n] #若备忘录有,则直接得到n层台阶的答案
elif n <= 0: #从这里开始的四行是用来判断“边界问题”
if n == 0: #若刚好跳完台阶,则这样算一种方法
self.m = 1 #m变成1,代表是一种可行方法
else: #有可能跳的台阶超过实际台阶数
self.m = 0 #m为0,代表不可行
elif n>0: #这里两行是用于规划转移方程式(其实这里很简单),青蛙只有两种可能,跳一层或者跳两层。
self.m = self.dp(n-2)+self.dp(n-1) #当前n层台阶的解个数 等于 n-1层台阶的解 + n-2层台阶的解
self.mark[n] = self.m #把m放入备忘录,下次若是再次是n层台阶,则不用计算直接取备忘录的数。(优化)
return self.m #返回

if name == ‘main‘:
dp1 = Dp1(100)

所以其实一开始没有写错,是6个思考点,是经过优化的6个思考点,第五步是做备忘录,第六步是时间分析。
总结步骤的话,根据博客作者的话来说,就是:
1、构造问题所对应的过程。

   2、思考过程的最后一个步骤,看看有哪些选择情况。

   3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。

   4、使得子问题符合“最优子结构”。

   5、找到边界,考虑边界的各种处理方式。

   6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。

   7、考虑如何做备忘录。

   8、分析所需时间是否满足要求。

   9、写出转移方程式。

一开始的金矿例题是用C++实现的,我这里换成python。
这是根据开头提到的博客作者代码思路改编的。
class Dp(object):
def init(self, n, m, peopleneed, gold): #n是总人数,m是金矿数
self.peopleneed = peopleneed #每一个金矿开挖需要的人数
self.gold = gold #每一个金矿的金矿数
self.maxgold =[[-1 for i in xrange(n)] for i in xrange(m)] #初始化备忘录,创建一个m行n列的二维数组。
print self.getmaxgold(n,m) #n,m减一是因为数组是从0开始

def getmaxgold(self,n,m):
    #retmaxgold = 0
    if self.maxgold[m-1][n-1] != -1:
        retmaxgold = self.maxgold[m-1][n-1]
    elif m == 0:
        if (n >= self.peopleneed[m-1]):
            retmaxgold = self.gold[m-1]
        else:
            retmaxgold = 0
    elif n >= self.peopleneed[m-1]:
        retmaxgold = max(self.getmaxgold(n - self.peopleneed[m-1],m -1)+self.gold[m-1],self.getmaxgold(n,m-1))
    else:
        retmaxgold = self.getmaxgold(n,m-1)
    self.maxgold[m-1][n-1] = retmaxgold
    return retmaxgold

if name == ‘main‘:
peopleneed = []
gold = []
str = raw_input(“”)
try:
str = str.split(” “)
n = int(str[0])
m = int(str[1])
for i in range(m):
goldmount = raw_input(“”)
goldmount = goldmount.split(” “)
peopleneed.append(int(goldmount[0]))
gold.append(int(goldmount[1]))
except:
print “输入格式错误”
dp = Dp(n, m, peopleneed, gold)

输入格式为:
100 5

77 92

22 22

29 87

50 46

99 90

答案: 133

下面找了一些题目练习(动态规划只有练习才能提高…只能不断练习)

例题来源:http://www.cnblogs.com/wuyuegb2312/p/3281264.html#q1

1.硬币找零
难度评级:★

  假设有几种硬币,如1、3、5,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。

代码实现:

coding=utf-8

class Dp2(object):
def init(self,money):
self.mark = [0 for _ in xrange(money+1)] #备忘录
print self.dp(money) #开始递归
def dp(self,money):
self.coin = 0 #需要的硬币数为0
if self.mark[money] != 0: #在备忘录中寻找该金额下的最少硬币找零数,若存在,则取出
self.coin = self.mark[money]
elif money <= 0: #边界问题
if money == 0: #如果金额为零,则代表刚好算是一种找零方法
self.coin = 0 #这里的0不是代表硬币数为0,而是代表这种方法可行,因为在下面已经有加1,若是这里coin为1,结果就会比答案多1
else:
self.coin = float(“inf”) #若是金额为负数,即“拿多了”,这种方法不可行,则硬币消耗数为 无穷大
elif money > 0:
self.coin = min(self.dp(money-1),self.dp(money-3),self.dp(money-5))+1 #递归,找出最少的可以凑齐金额数money的方法
self.mark[money] = self.coin #做备忘录
return self.coin

if name == ‘main‘:
dp2 = Dp2(65) #找零钱

猜你喜欢

转载自blog.csdn.net/weixin_43127762/article/details/82378373