贪心算法(又称贪婪算法)是指,在对问题求解时,老是作出在当前看来是最好的选择。也就是说,不从总体最优上加以考虑,他所作出的的时在某种意义上的局部最优解。html
贪心算法并不保证会获得最优解,可是在某些问题上贪心算法的解就是最优解。要会判断一个问题可否用贪心算法来计算。贪心算法和其余算法比较有明显的区别,动态规划每次都是综合全部问题的子问题的解获得当前的最优解(全局最优解),而不是贪心地选择;回溯法是尝试选择一条路,若是选择错了的话能够“反悔”,也就是回过头来从新选择其余的试试。python
假设商店老板须要找零 n 元钱,钱币的面额有100元,50元,20元,5元,1元,如何找零使得所需钱币的数量最少?(注意:没有10元的面额)git
那要是找376元零钱呢? 100*3+50*1+20*1+5*1+1*1=375github
代码以下:算法
# t表示商店有的零钱的面额 t = [100, 50, 20, 5, 1] # n 表示n元钱 def change(t, n): m = [0 for _ in range(len(t))] for i, money in enumerate(t): m[i] = n // money # 除法向下取整 n = n % money # 除法取余 return m, n print(change(t, 376)) # ([3, 1, 1, 1, 1], 0)
常见的背包问题有整数背包和部分背包问题。那问题的描述大体是这样的。数组
一个小偷在某个商店发现有 n 个商品,第 i 个商品价值 Vi元,重 Wi 千克。他但愿拿走的价值尽可能高,但他的背包最多只能容纳W千克的东西。他应该拿走那些商品?app
0-1背包:对于一个商品,小偷要么把他完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走屡次(商品为金条)函数
分数背包:对于一个商品,小偷能够拿走其中任意一部分。(商品为金砂)学习
举例:spa
显然,贪心算法对于分数背包确定能获得最优解,咱们计算每一个物品的单位重量的价值,而后将他们降序排序,接着开始拿物品,只要装得下所有的该类物品那么就能够全装进去,若是不能所有装下就装部分进去直到背包装满为止。
而对于此问题来讲,显然0-1背包确定装不满。即便偶然能够,可是也不能知足全部0-1背包问题。0-1背包(又叫整数背包问题)还能够分为两种:一种是每类物品数量都是有限的(bounded)。一种是数量无限(unbounded),也就是你想要的多少有多少,这两种都不能使用贪心策略。0-1背包是典型的第一种整数背包问题。
分数背包代码实现:
# 每一个商品元组表示(价格,重量) goods = [(60, 10), (100, 20), (120, 30)] # 咱们须要对商品首先进行排序,固然这里是排好序的 goods.sort(key=lambda x: x[0]/x[1], reverse=True) # w 表示背包的容量 def fractional_backpack(goods, w): # m 表示每一个商品拿走多少个 total_v = 0 m = [0 for _ in range(len(goods))] for i, (prize, weight) in enumerate(goods): if w >= weight: m[i] = 1 total_v += prize w -= weight # m[i] = 1 if w>= weight else weight / w else: m[i] = w / weight total_v += m[i]*prize w = 0 break return m, total_v res1, res2 = fractional_backpack(goods, 50) print(res1, res2) # [1, 1, 0.6666666666666666]
有 n 个非负数,将其按照字符串拼接的方式拼接为一个整数。如何拼接可使得获得的整数最大?
例如:32, 94, 128, 1286, 6, 71 能够拼接成的最大整数为 94716321286128.
注意1:字符串比较数字大小和整数比较数字大小不同!!! 字符串比较大小就是首先看第一位,大的就大,但是一个字符串长,一个字符串短如何比较呢?好比128和1286比较
思路以下:
# 简单的:当两个等位数相比较 a = '96' b = '97' a + b if a > b else b + a # 当出现了下面的不等位数相比较,如何使用贪心算法呢? # 咱们转化思路,拼接字符串,比较结果 a = '128' b = '1286' # 字符串相加 a + b = '1281286' b + a = '1286128' a + b if a + b > b + a else b + a
数字拼接代码以下:
from functools import cmp_to_key li = [32, 94, 128, 1286, 6, 71] def xy_cmp(x, y): # 其中1表示x>y,-1,0同理 if x+y < y+x: return 1 elif x+y > y+x: return -1 else: return 0 def number_join(li): li = list(map(str, li)) li.sort(key=cmp_to_key(xy_cmp)) return "".join(li) print(number_join(li)) # 94716321286128
下面学习一下Python中一个比较好用的模块,就是functools 中的 cmp_to_key函数,这里的 cmp_to_key就是在Python3中使用的,在Python2中就是 cmp函数。
它的具体做用就是比较函数。固然上面函数也能够写成下面形式:
def largestNumber(self, nums): from functools import cmp_to_key temp = list(map(str, nums)) temp.sort(key=cmp_to_key(lambda x, y: int(x + y) - int(y + x)), reverse=True) return ''.join(temp if temp[0] != '0' else '0')
上面函数有两个传入的参数 x, y,当 x>y 时返回1 等于时候返回0,不然返回-1。其实我最上面的函数比较明显。它在list的工做机制就是将列表中的元素去两两比较,当 cmp返回的时正数时交换两元素。
假设有 n 个活动,这些活动要占用同一片场地,而场地在某时刻只能供一个活动使用。
每个活动都有一个开始时间 Si 和结束时间 Fi (题目中时间以整数表示)表示活动在 [Si, fi) 区间占用场地。(注意:左开右闭)
问:安排哪些活动可以使该场地举办的活动的个数最多?
贪心结论:最早结束的活动必定是最优解的一部分。
证实:假设 a 是全部活动中最早结束的活动,b是最优解中最早结束的活动。
若是 a=b,结论成立
若是 a!=b,则 b 的结束时间必定晚于 a 的结束时间,则此时用 a 替换掉最优解中的 b ,a 必定不与最优解中的其余活动时间重叠,所以替换后的解也是最优解。
代码以下:
# 一个元组表示一个活动,(开始时间,结束时间) activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)] # 保证活动是按照结束时间排好序,咱们能够本身先排序 activities.sort(key=lambda x:x[1]) def activity_selection(a): # 首先a[0] 确定是最先结束的 res = [a[0]] for i in range(1, len(a)): if a[i][0] >= res[-1][1]: # 当前活动的开始时间小于等于最后一个入选活动的结束时间 # 不冲突 res.append(a[i]) return res res = activity_selection(activities) print(res)
求最大子数组之和的问题就是给定一个整数数组(数组元素有负有正),求其连续子数组之和的最大值。下面使用贪心算法逐个遍历。
代码以下:
def maxSubarray(li): s_max, s_sum = 0, 0 for i in range(len(li)): s_sum += li[i] s_max = max(s_max, s_sum) if s_sum < 0: s_sum = 0 return s_max
约数:若是整数 a 能被整数 b 整除,那么 a 叫作 b 的倍数,b 叫作 a 的约数。
最大公约数(Greatest Common Divisor):给定两个整数 a, b,两个数的全部公共约数中的最大值即为最大公约数。
例如:12和16的最大公约数是 4 。
欧几里得算法又称为展转相除法,用于计算两个正整数a,b的最大公约数。
欧几里得算法运用了这样一个等价式(设 gcd(a, b)表明 a 和 b 的最大公约数,mod()表明取余运算或模运算)则:
gcd(a, b) = gcd(b, a mod b ) = gcd(b, a%b)
也就是说 m , n 的最大公约数等于他们相除余数(r)和 n 的最大公约数。
例如:gcd(60, 21) = gcd(21, 18) = gcd(18, 3) = gcd(3, 0) = 3
意思就是 60对21取余18,同理21对18余3,18对3取余0,因此3为两个数的最大公约数。
咱们的证实分为两步。第一步,证实gcd(a, b)是b, a%b 的一个公约数。第二步,证实这个公约数是最大的。
1,证实gcd(a, b)是b, a%b 的一个公约数
1,由于任意两个正整数都有最大公因数,设为 d。
2,将 a , b 分别用最大公因数 d 来表示为 a = k1*d b = k2*d (k1,k2是两个常数)
3,设 a = k*b + c (也就是a 除以 b 商 k 余 c),而后把a = k1*d b = k2*d 两个式子中的 a,b代入式子,获得:
c = a - k*b = k1*d - k * k2 * d,而后再提取公因数 d,获得 c = (k1 - k2 * k)*d,这就说明,c也就是 a%b有 d 这个约数,由于开始咱们设 任意两个数都有最大公约数d,因此 gcd(a, b) 是 b, a%b 的一个公约数。
4,由此能够获得 c 是最大公因数 d 的倍数,得证:gcd(a, b) = gcd(b, a mod b)。因此以此类推,能够将 m n中较大的数用较小的数的余数 r 替换,实现了降维,因此有了E3的步骤。
2,证实咱们求出来的公约数是最大的
1,数学是一门严谨的学科,咱们须要严谨的正面,咱们知道 c(a%b) = k1*d - k * k2 * d b = k2*d,因此咱们只须要证实k1-k*k2, k2互质便可。
2,这里能够用到反证法,咱们假设 k1 - k*k2 = q*t k2=p*t,再讲这个k1 代入最开始的 a=k1*d ,获得 a=(q*t + k*k2)*d,再利用乘法分配律获得: a = q*t*d + k*k2*d,这时候咱们发现,k2*d就是b,将其代入,获得 a=q*t*d + b*d
3,咱们在将k2 = p*t代入开始的b = k2*d,获得b = p*t*d,再把这个式子代到a = q*t*d+b*d.获得了:a = q*t*d+p*t*d.提取公因数:a=(q+p)*t*d
4,再和b=p*t*d比较,发现他们的最大公因数变成了t*d和开始矛盾,因此假设不成立,反证成功!
1,欧几里得:展转相除法(欧几里得算法)
2,《九章算术》:更相减损术
代码以下:
# 递归法:保证a>b def gcd(a, b): if b == 0: return a else: return gcd(b, a % b) # 递推法 def gcd1(a, b): if a < b: a, b = b, a while b > 0: r = a % b a = b b = r return a
由于这是一个伪递归,因此时间复杂度不高。
利用欧几里得算法实现一个分数类,支持分数的四则运算。
代码以下:
# _*_coding:utf-8_*_ class Fraction: def __init__(self, a, b): self.a = a self.b = b x = self.gcd(a, b) self.a /= x self.b /= x # 最大公约数 def gcd(self, a, b): while b > 0: r = a % b a = b b = r return a # 最小公倍数 def zgs(self, a, b): # 12 16 -> 4 # 3 * 4 * 4=48 x = self.gcd(a, b) return (a * b / x) # 加法的内置方法 def __add__(self, other): # 1/12 + 1/20 a = self.a b = self.b c = other.a d = other.b fenmu = self.zgs(b, d) femzi = a * (fenmu / b) + c * (fenmu / d) return Fraction(femzi, fenmu) def __str__(self): return "%d/%d" % (self.a, self.b) f = Fraction(30, 16) print(f)
欧几里得算法是计算两个数最大公约数的传统算法,不管从理论仍是实际效率上都是很好地。可是却有一个致命的缺陷,这个缺陷在素数比较小的时候通常是感觉不到的,只有在大素数时才会显现出来。
通常实际应用中的整数不多会超过64位(固然如今已经容许128位),对于这样的整数,计算两个数之间的模很简单。对于字长为32位的平台,计算两个不超过32位的整数的模,只须要一个指令周期,而计算64位如下的整数模,也不过几个周期而已。可是对于更大的素数,这样的计算过程就不得不禁用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用相似于多位数除法手算过程当中的试商法,这个过程不但复杂,并且消耗了不少CPU时间。对于现代密码算法,要求计算128位以上的素数的状况比比皆是,设计这样的程序迫切但愿可以抛弃除法和取模。
由J. Stein 1961年提出的Stein算法很好的解决了欧几里德算法中的这个缺陷,Stein算法只有整数的移位和加减法,为了说明Stein算法的正确性,首先必须注意到如下结论:
代码以下:
def gcd_Stein(a, b): if a < b: a, b = b, a if (0 == b): return a if a % 2 == 0 and b % 2 == 0: return 2 * gcd_Stein(a/2, b/2) if a % 2 == 0: return gcd_Stein(a / 2, b) if b % 2 == 0: return gcd_Stein(a, b / 2) return gcd_Stein((a + b) / 2, (a - b) / 2)
参考文献:https://www.cnblogs.com/jason2003/p/9797750.html
https://www.cnblogs.com/Dragon5/p/6401596.html