1,贪心算法

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

  贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解。要会判断一个问题能否用贪心算法来计算。贪心算法和其他算法比较有明显的区别,动态规划每次都是综合所有问题的子问题的解得到当前的最优解(全局最优解),而不是贪心地选择;回溯法是尝试选择一条路,如果选择错了的话可以“反悔”,也就是回过头来重新选择其他的试试。

1.1  找零问题

  假设商店老板需要找零 n 元钱,钱币的面额有100元,50元,20元,5元,1元,如何找零使得所需钱币的数量最少?(注意:没有10元的面额)

  那要是找376元零钱呢? 100*3+50*1+20*1+5*1+1*1=375

  代码如下:

# 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)

1.2  背包问题

  常见的背包问题有整数背包和部分背包问题。那问题的描述大致是这样的。

  一个小偷在某个商店发现有 n 个商品,第 i 个商品价值 Vi元,重 Wi 千克。他希望拿走的价值尽量高,但他的背包最多只能容纳W千克的东西。他应该拿走那些商品?

  0-1背包:对于一个商品,小偷要么把他完整拿走,要么留下。不能只拿走一部分,或把一个商品拿走多次(商品为金条)

  分数背包:对于一个商品,小偷可以拿走其中任意一部分。(商品为金砂)

举例:

  对于 0-1 背包 和 分数背包,贪心算法是否都能得到最优解?为什么?

  显然,贪心算法对于分数背包肯定能得到最优解,我们计算每个物品的单位重量的价值,然后将他们降序排序,接着开始拿物品,只要装得下全部的该类物品那么就可以全装进去,如果不能全部装下就装部分进去直到背包装满为止。

  而对于此问题来说,显然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]

  

1.3  拼接最大数字问题

  有 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  cmp_to_key函数

  下面学习一下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返回的时正数时交换两元素。

  

1.4  活动选择问题

  假设有 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)

 

1.5  最大子序和

  求最大子数组之和的问题就是给定一个整数数组(数组元素有负有正),求其连续子数组之和的最大值。下面使用贪心算法逐个遍历。

 代码如下:

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

2,欧几里得算法——最大公约数

2.1,最大公约数的定义

  约数:如果整数 a 能被整数 b 整除,那么 a 叫做 b 的倍数,b 叫做 a 的约数。

  最大公约数(Greatest  Common  Divisor):给定两个整数 a, b,两个数的所有公共约数中的最大值即为最大公约数。

  例如:12和16的最大公约数是 4 。

2.2,欧几里得算法如下:

  欧几里得算法又称为辗转相除法,用于计算两个正整数a,b的最大公约数。

  • E:设两个正整数a, b,且已知a>b
  • E1:令r = a%b('%'代表取余)
  • E2:若r=0(即n整除m),结束运算,n即为结果
  • E3:否则令a=b,b=r,并返回步骤E1

  欧几里得算法运用了这样一个等价式(设 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为两个数的最大公约数。

2.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和开始矛盾,所以假设不成立,反证成功!

2.4,如何计算最大公约数?

  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

  因为这是一个伪递归,所以时间复杂度不高。

2.5,应用:实现分数计算

  利用欧几里得算法实现一个分数类,支持分数的四则运算。

  代码如下:

# _*_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)

  

2.7 欧几里得算法的缺点

  欧几里得算法是计算两个数最大公约数的传统算法,无论从理论还是实际效率上都是很好地。但是却有一个致命的缺陷,这个缺陷在素数比较小的时候一般是感受不到的,只有在大素数时才会显现出来。

  一般实际应用中的整数很少会超过64位(当然现在已经允许128位),对于这样的整数,计算两个数之间的模很简单。对于字长为32位的平台,计算两个不超过32位的整数的模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU时间。对于现代密码算法,要求计算128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。

  由J. Stein 1961年提出的Stein算法很好的解决了欧几里德算法中的这个缺陷,Stein算法只有整数的移位和加减法,为了说明Stein算法的正确性,首先必须注意到以下结论:

  gcd(a,a)=a,也就是一个数和其自身的公约数仍是其自身。
  gcd(ka,kb)=k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换。特殊地,当k=2时,说明两个偶数的最大公约数必然能被2整除。
  当k与b互为质数,gcd(ka,b)=gcd(a,b),也就是约掉两个数中只有其中一个含有的因子不影响最大公约数。特殊地,当k=2时,说明计算一个偶数和一个奇数的最大公约数时,可以先将偶数除以2。

  代码如下:

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)

  

传送门:代码的GitHub地址:https://github.com/LeBron-Jian/BasicAlgorithmPractice

参考文献:https://www.cnblogs.com/jason2003/p/9797750.html

https://www.cnblogs.com/Dragon5/p/6401596.html

python常用算法(6)——贪心算法,欧几里得算法的更多相关文章

  1. 简单学完HTML+CSS+JS,现在开始看算法(第四版)----欧几里得算法

    欧几里得算法 package euclidean_algorithm; import java.util.Scanner; /** * @author ALazy_cat * 欧几里得算法的自然语言描 ...

  2. 算法导论----贪心算法,删除k个数,使剩下的数字最小

    先贴问题: 1个n位正整数a,删去其中的k位,得到一个新的正整数b,设计一个贪心算法,对给定的a和k得到最小的b: 一.我的想法:先看例子:a=5476579228:去掉4位,则位数n=10,k=4, ...

  3. [算法导论]贪心算法(greedy algorithm)

    转载请注明出处:http://www.cnblogs.com/StartoverX/p/4611544.html 贪心算法在每一步都做出当时看起来最佳的选择.也就是说,它总是做出局部最优的选择,寄希望 ...

  4. 数据结构与算法之贪心算法 C++实现

    1.基本思路:从问题的某一个初始解触发逐步逼近给定的目标,以尽可能快的求得更好的解. 当达到算法中某一步不能再继续前进时.就停止算法,给出近似值.也就是说贪心算法并不从总体最优考虑,它所作出的选择仅仅 ...

  5. 《Java算法》贪心算法

    贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解. 贪心算法的经典案例: 跳跃游戏: 给定一个非负整 ...

  6. dijkstra算法(贪心算法)——解决最短路径问题

    最短路径 给定一张带权图和其中的一个点(作为源点),求源点到其余顶点的最短路径 基本思想 1)源点u,所有顶点的集合V,集合S(S中存有的顶点,他们到源点的最短路径已经确定,源点u默认在S中),集合V ...

  7. 贪心算法(Greedy Algorithm)

    参考: 五大常用算法之三:贪心算法 算法系列:贪心算法 贪心算法详解 从零开始学贪心算法 一.基本概念: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以 ...

  8. 剑指Offer——贪心算法

    剑指Offer--贪心算法 一.基本概念 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解.虽然贪心算法不能对 ...

  9. 基于贪心算法求解TSP问题(JAVA)

    概述 前段时间在搞贪心算法,为了举例,故拿TSP来开刀,写了段求解算法代码以便有需之人,注意代码考虑可读性从最容易理解角度写,没有优化,有需要可以自行优化! 详细 代码下载:http://www.de ...

  10. [C++]单源最短路径:迪杰斯特拉(Dijkstra)算法(贪心算法)

    1 Dijkstra算法 1.1 算法基本信息 解决问题/提出背景 单源最短路径(在带权有向图中,求从某顶点到其余各顶点的最短路径) 算法思想 贪心算法 按路径长度递增的次序,依次产生最短路径的算法 ...

随机推荐

  1. Hbase入门(一)——初识Hbase

    本文将介绍大数据的知识和Hbase的基本概念,作为大数据体系中重要的一员,Hbase弥补了Hadoop只能离线批处理的不足,支持存储小文件,随机检索.而这种特性使得Hbase对于实时计算体系的事件存储 ...

  2. [VB.NET Tips]程序的启动和终止

    当执行一个VB.NET应用程序时,CLR会把IL翻译成x86指令,并且寻找一个名为Main的方法. 并从该方法开始执行程序.Main方法也称为程序的"入口"(entry point ...

  3. /bin/java: 没有那个文件或目录spark/bin/spark-class:行71: /usr/java/jdk1.8

    1.检查java环境有没有问题 2.1没问题后检查文件的编码是否有问题

  4. Docker下实战zabbix三部曲之一:极速体验

    对于想学习和实践zabbix的读者来说,在真实环境搭建一套zabbix系统是件费时费力的事情,本文内容就是用docker来缩减搭建时间,目标是让读者们尽快投入zabbix系统的体验和实践: 环境信息 ...

  5. 2018年蓝桥杯java b组第八题

    标题:日志统计 小明维护着一个程序员论坛.现在他收集了一份"点赞"日志,日志共有N行.其中每一行的格式是: ts id 表示在ts时刻编号id的帖子收到一个"赞" ...

  6. 使用 BeanDefinition 描述 Spring Bean

    什么是BeanDefinition 在Java中,一切皆对象.在JDK中使用java.lang.Class来描述类这个对象. 在Spring中,存在bean这样一个概念,那Spring又是怎么抽象be ...

  7. ueditor的初始化赋值

    ue.ready(function () {ue.setContent('初始内容'); //赋值给UEditor });

  8. asp.net core 3.0 更新简记

    asp.net core 3.0 更新简记 asp.net core 3.0 更新简记 Intro 最近把活动室预约项目从 asp.net core 2.2 更新到了 asp.net core 3.0 ...

  9. Nginx的基本安装配置

    Centos7安装nginx 升级nginx 升级可能遇到问题(我没有遇到, 参考的另一篇文章描述的) 检查nginx版本, 确认安装成功 nginx配置文件 虚拟主机配置 配置文件中可以用的全局变量 ...

  10. 自学导航页(待续ing)

    1 博客导航1.1 linuxlinux全线教程–提供了linux教程,服务器管理教程,BSD教程,还有编程语言(C/Java/Python/Perl),以及网络等全栈学习教程 1.2 存储技术NoS ...