02 《算法图解——像小说一样有趣的算法书》3


8 贪婪算法

贪婪算法的优点:简单易行!每步都采用最优的做法,每步都选择最优解,最终得到全局最优解。贪婪算法并非在任何地方行之有效,但它易于实现!
示例: 排课表

假设有如下课程表,你希望尽可能多的课程安排在某间教室上。

课程开始时间结束时间
美术9 am10 am
英语9:30 am10:30 am
数学10 am11 am
计算机10:30 am11:30 am
音乐11 am12 pm

具体的做法:
(1)选出结束最早的课,它就是要在这间教室上的第一堂课
(2)接下来,必须选择第一堂课结束后才开始上的课。同样,你选择结束最早的课,这将是要在这间教室上的第二堂课。

**答案:**这间教室上如下三堂课

课程开始时间结束时间
美术9 am10 am
数学10 am11 am
音乐11 am12 pm

贪婪算法可得到非常接近的解
(1)选出这样一个广播台,即它覆盖了最多的未覆盖州。即便这个广播台覆盖了一些已覆盖的州,也没有关系。
(2)重复第一步,直到覆盖了所有的州。

这是一种近似算法(approximation algorithm)。在获得精确需要的时间太长时,可使用近似算法。判断近似算法优劣的标准如下:

  • 速度有多快
  • 得到的近似解与最优解的接近程度。
    贪婪算法师不错的选择,它们不仅简单,而且通常运行速度很快。在这个例子中,贪婪算法的运行时间为O(n2),n为广播台的数量。

代码如下

/===========================  1. 准备工作  ============================/
//创建一个列表,其中包含要覆盖的州。
//同样的元素只能包含一次,即集合不能包含重复的元素。
stated_needed=set(["mt","wa","or","id","nv","ut","ca","az"])

//还需要广播台清单,用散列表来表示。
//其中键为广播台的名称,值为广播台覆盖的州。
stations={}
stations["kone"]=set(["id","nv","ut"])
stations["ktwo"]=set(["wa","id","mt"])
stations["kthree"]=set(["or","nv","ca"])
stations["kfour"]=set(["nv","ut"])
stations["kfive"]=set(["ca","az"])

//需要使用一个集合来存储最终选择的广播台。
final_stations=set()

/===========================  2. 计算答案  ============================/
//接下来计算要使用的哪些广播台
//正确的解有多个。你需要遍历所有的广播台,从中选择覆盖了最多的未覆盖的广播台。
while states_needed:
    best_station=None       # 当前广播台
    states_covered=set()    # 已覆盖的州
    for station,states_for_station in stations.items():
    # (station,states_for_station)=(key="广播台",value={[对应覆盖的州]})
        covered = states_needed & states_for_station
        # covered(有新的覆盖的州)= 未覆盖的州 & 新的广播台对应要覆盖的州
        if len(covered) > len(states_covered):  # len(covered) > 0
            best_station=station     		# key值保存在当前广播台best_station中
            states_covered=covered    		# covered(新的覆盖的州)保存在states_covered
            final_stations.add(best_station)    # key值保存在选择的广播台
            states_needed-=states_covered       # states_needed未覆盖的州

>>> print(final_stations)
> {'ktwo', 'kone', 'kthree', 'kfive'}

上面代码涉及的基本知识点:
// ============================  set()函数的用法  ============================
>>> arr=[1,2,2,3,3,3,3,3,4]
>>> set(arr)
> set([1,2,3,4])

// ============================  交、并、差集(& | -============================
>>> fruits=set(["avocado","tomato","banana"])
>>> vegetables=set(["beets","carrots","tomato"])
>>> print(fruits | vegetables)
> set(["avocado","beets","carrots","tomato","banana"])
>>> print(fruits & vegetables)
> set(["tomato"]
>>> print(fruits - vegetables)
> set(["avocado","banana"])

NP完全问题
定义:计算所有的解,并选出最小/最短的那个。

判断问题是不是NP完全问题?

  • 元素较少时,算法的运行速度非常快,但随着元素数量增加,速度会变得非常慢。
  • 涉及“所有组合”问题 ,通常是NP完全问题。
  • 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
  • 如果问题涉及序列(旅行商问题中的城市序列)且难以解决。这可能是NP完全问题。
  • 如果问题涉及集合(如广播台集合)且难以解决。这可能是NP完全问题。
  • 如果问题可以转换为集合覆盖问题或旅行商问题,那肯定是NP完全问题。


9 动态规划

(1)动态规划可帮助你在给定约束条件下找到最优解。在背包问题中,你必须在背包容量给定的情况下,偷到价值最高的商品。
(2)在问题可分解为彼此独立且离散的子问题时,就可使用动态规划来解决。
(3)每种动态规划解决方案都涉及网格。
(4)单元格的值通常就是你要优化的值。在背包问题中,单元格的值为商品的价值。
(5)每个单元格都是一个子问题,因此你应考虑如何将问题分成子问题,这有助于你找出网格的坐标轴。


绘制表格

费曼算法: (Feynman algorithm)
① 将问题写下来;
② 好好思考;
③ 将答案写下来。


if word_a[i]==word_b[j]:		# 两个字母相同
	cell[i][j]=cell[i-1][j-1]+1
else:							# 两个字母不同
	cell[i][j]=0

在这里插入图片描述


动态规划的实际应用

  • 生物学家根据最长公共序列来确定DNA链的相似性,进而判断两种动物或疾病有多相似。最长公共序列还被用来寻找多发性硬化症治疗方案。
  • 你是用过git diff等命令吗?他们指出两个文件的差异,也是用动态规划实现的。
  • 前面讨论了字符串的相似程度。编辑距离(levenshtein distance)指出了两个字符串的相似程度,也是使用动态规划计算得到的。编辑距离算法的用途很多,从拼写检查到判断用户上传的资料是否是盗版,都在其中。
  • 你使用过诸如Microsoft Word等具有段字功能的应用程序吗?它们如何确定在什么地方段字以确保行长一致呢?使用动态规划。


10 K最近邻算法

K最近邻算法(k-nearest neighbours,KNN)

橙子还是柚子
① 你需要对一个水果进行分类;
② 你查看它三个最近的邻居;
③ 在这些邻居中,橙子多于柚子,因此它很可能是橙子。

K最近邻-思路

创建推荐系统

为用户创建一个电影推荐系统,请将所有的用户放入一个图表中。这些用户的位置取决于其喜好,因此喜好相似的用户距离较近。假设你要向另一个人推荐电影,可以找出五位与他最接近的用户。
在这里插入图片描述
假设在对电影的喜好方面,Justin、JC、Joey、Lance和Chris都与Priyanka差不多,因此他们喜欢的电影很可能Priyanka也喜欢!有了这样的图表以后,创建推荐系统就将易如反掌:只要Justin喜欢的电影,就将其推荐给Priyanka。

特征抽取
合适的特征

  • 与要推荐的电影紧密相关的特征;
  • 不偏不倚的特征(如果只让用户给喜剧片打分,就无法判断他们是否喜欢动作片)。

机器学习的简介

OCR指的是光学字符识别(optional character recognition),这意味着你可拍摄页面的照片,计算机将自动识别出其中的文字。Google使用OCR技术实现图书数字化。
OCR是如何工作的呢?
请看这个数字:“7
如何自动识别出这个数字是什么呢?可使用KNN。
(1)浏览大量的数字图像,将这些数字的特征提取出来。
(2)遇到新图像时,你提取该图像的特征,再找出它最近的邻居是谁!
一般而言,OCR算法提取线段、点和曲线等特征。遇到新字符时,可从中提取同样的特征。在这里插入图片描述
朴素贝叶斯分类器(Navie Bayes classifier)
垃圾邮件过滤器使用的一种简单算法。在这里插入图片描述
假设你收到一封邮件主题为 “ collect your million dollars now!”的邮件,这是垃圾邮件吗?你可研究这个句子中的每个单词,看看它在垃圾邮件中出现的概率是多少。例如,使用这个非常简单的模型时,发现只有单词million在垃圾邮件中出现过。朴素贝叶斯分类器能计算出邮件为垃圾邮件的概率,其应用领域与KNN相似。

预测股票市场
使用机器学习来预测股票市场真的很难。对于股票市场,如何挑选合适的特征呢?股票昨天涨了,今天也会涨,这样的特征合适吗?又或者每年五月份股票市场都已绿盘报收,这样的预测可行吗?根据以往的数据来预测未来方面,没有万无一失的方法。未来很难预测,由于涉及的变数太多,这几乎是不可能完成的任务。



11 接下来如何做

  1. 反向索引
    搜索引擎的工作原理。假设你有三个网页,根据这些内容创建一个散列表。
    在这里插入图片描述
    散列表的键为单词,值为包含指定单词的页面。现在假设有用户搜索hi,在这种情况下,搜索引擎需要检查哪些页面包含hi。搜索引擎发现A和B页面包含hi,因此将这些页面作为搜索结果呈现给用户。现在假设用户搜索there。你知道,页面A和C包含它。
    反向索引(inverted index):一个散列表,将单词映射到包含它的页面,常用于搜索引擎。

  2. 傅里叶变换
    理念简单,运用广泛。给定一首歌曲,傅里叶变换能够将其中的各种频率分离出来。可强化你关心的部分,强化低音隐藏高音,或者准确地指出各个音符对整个歌曲的贡献,让你能够将不重要的音符删除,这就是mp3格式的工作原理。
    傅里叶变换非常适合用于处理信号,可使用它来压缩音乐。数字信号并非只有音乐一种,JPG也是一种压缩,也采用了刚才说的工作原理。傅里叶变换还被用来地震预测和DNA分析。

  3. 并行算法
    为了提高算法速度

  4. MapReduce
    MapReduce是一种分布式算法,你可以通过流行的开源工具Apache Hadoop来使用它。

分布式算法为何很有用?
分布式算法非常适合在短时间内完成海量工作,其中的MapReduce基于两个简单的理念:映射(map)函数归并(reduce)函数

映射(map)函数
映射函数接受一个数组,并对其中的每个元素执行同样的处理。例如:下面的映射函数将数组的每个元素翻倍。

>>> arr1 = [1 ,2 ,3 ,4 ,5 ]
>>> arr2 = map( lambda x : 2 * x, arr1 )
> [2,4,6,8,10]

归并(reduce)函数
归并是将多项转化为一项,映射是将一个数组转化为另一个数组。

>>> arr1 = [1 ,2 ,3 ,4 ,5 ]
>>> arr2 = reduce( lambda x , y : x + y , arr1 )
> 15

在上述示例中,加ing所有的元素相加:1+2+3+4+5=15

MapReduce使用这两个简单概念在多台计算机上执行数据查询。数据集很大,包含数十亿行时,使用MapReduce只需几分钟就可获得查询结果,传统方法需要耗费数小时。

  1. 布隆过滤器和HyberLogLog
  • 你管理着网站Reddit。每当有人发布链接时,你都要检查它以前是否发布过,因为之前未发布过的故事更有价值。
  • 又假设你在Google负责搜集页面,但只想搜集新出现的网页,因此需要判断网页是否搜集过。
  • 在假设你管理着提供网址缩短服务的bit.ly,要避免将用户重定向到恶意网站,你有一个清单。你需要确定用户重定向到的URL是否在这个清单中。

这些是同一类型的问题,涉及庞大的集合。
给定一个元素,你要确定它是否包含在这个集合中。为快速做出反应,可使用散列表。例如,Google可能有一个庞大的散列表,其中的键是已搜集的网页。要判断是否已搜集adit.io,可在这个散列表中查找到它。
adit.io是这个散列表中的一个键,这说明已搜集它。散列表的平均查找时间为O(1),即查找时间是固定的,非常好!
只是Google需要建立数万亿个网页的索引,因此这个散列表非常庞大,需要占用大量的存储空间。Reddit和bit.ly也面临着这样的问题。面临海量的数据,你需要创造性的解决方案!

布隆过滤器
布隆过滤器是一种概率性的数据结构,它提供的答案有可能不对,但很有可能是正确的。为判断以前是否已搜集,可不使用散列表,而是用布隆过滤器。使用散列表时,答案绝对可靠,而使用布隆过滤器时,答案却是很可能是正确的。

  • 可能出现报错的情况,即Google可能指出“这个网站已搜集”,但实际上没有搜集。
  • 不可能出现漏报的情况,即如果布隆过滤器说“这个网站未搜集”,就肯定未搜集。

布隆过滤器的优点在于占用的存储空间很少。

HyberLogLog
HyberLogLog是一种类似于布隆过滤器的算法。
如果Google要计算用户执行不同搜索的数量,或者Amazon要计算当天用户浏览的不同商品的数量,要回答这些问题,需要耗用大量的空间!对Google来说,必须有一个日志,其中包含用户执行的不同搜索。有用户执行搜索时,Google必须判断该搜索是否包含在日志中:如果答案是否定的,就必须将其加入到日志中。即便只记录一天的搜索,这种日志也大得不得了。
HyberLogLog近似的计算集合中不同的元素数,与布隆过滤器一样,它不能给出准确答案,但也八九不离十时,就可以考虑概率型算法!


7. SHA算法
你有一个键,需要将其相关值放进数组中。在这里插入图片描述
你使用散列表函数来确定应将这个值放在数组的什么地方。
在这里插入图片描述
这样查找时间是固定的。当你想要知道指定键对应的值时,可再次执行散列函数,它将告诉你这个值储存在什么地方,需要的时间为O(1)。
在这个示例中,你希望散列函数的结果是均匀分布的。散列函数接受一个字符串,并返回一个索引号。

比较文件
另一种散列函数是安全散列算法(security hash algorithm,SHA)函数。给定一个字符串,SHA返回其散列值。
这个术语有点令人迷惑。SHA是一个散列函数,它生成一个散列值——一个较短的字符串。用于创建散列表的散列函数根据字符串生成数组索引,而SHA根据字符串生成另一个字符串。
对于不同的字符串,SHA生成的散列值都不同。(散列值也是字符串)
你可以使用SHA来判断两个文件是否相同,这在比较超大型文件时很有用。假设你有一个4GB的文件,并要检查朋友是否也有这个大型文件。为此,你不用通过电子邮件将这个大型文件发送给你朋友,而是计算它们的SHA散列值,再对结果进行比较。
在这里插入图片描述

检查密码
SHA还让你能在不知道原始字符串的情况下对其进行比较。例如,假设Gmail遭到袭击,攻击者窃取了所有的密码!你的密码暴露了吗?没有,因为Google存储的并非是密码,而是密码的SHA散列值!你输入密码时,Google计算其散列值,并将结果同其数据库中的散列值进行比较。

  1. 局部敏感的散列算法
    SHA还有一个重要特征,局部不敏感特征。
    假设你有一个字符串,并计算了其散列值。
    如果你修改其中一个字符,再计算其散列值,结果截然不同!
    攻击者无法通过比较散列值是否类似来破解密码。

  2. Diffie-Hellman密钥交换
    设计一套加密算法,如将a转化为1,b转化为2,以此类推。你发送消息 “4,15,7” ,可转化为 “ d, o, g " 。但我们必须就一套加密算法达成一致,这种方式才可行。
    即使我们每天修改,这样简单的加密算法也很容易用蛮力破解。

Diffie-Hellman解决了两个问题

  • 双方无需知道加密算法。他们不必会面协商要使用的加密算法。
  • 要破解加密的消息比登天还难。

Diffie-Hellman使用两个密钥:公钥和私钥。顾名思义,公钥就是公开的,可以发布到网站上,通过电子邮件发送给朋友,或使用其他任何方式来发布。有人要向你发送消息时,他使用公钥对其加密。加密后的消息只有使用私钥才能解密。只要只有你知道的私钥,就只有你能够解密消息!

  1. 线性规划
    线性规划用于在给定约束条件最大限度地改善指定指标。
    例如:你所在的公司生产两种产品:衬衫和手提袋。衬衫每件利润为2美元,需要消耗1米布料和5粒扣子;手提袋每个利润为3美元,需要消耗2米布料和2粒扣子。你有11米布料和20粒扣子,为最大限度地提高利润,该生产多少件衬衫,多少个手提袋呢?
    在这个例子中,目标是利润最大化,而约束条件是拥有的原材料数量。
    线性规划用Simplex算法,这个算法很复杂,也很有效。



《算法图解——像小说一样有趣的算法书》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值