课程 1 —算法工具箱—第 4 部分:动态编程
动态规划是一种非常强大的算法设计技术,用于解决许多指数问题。实际上,动态编程喜欢递归和“重用”。因此,为了解决动态编程的问题,我们分两步来做:
- 找出正确的重现(子问题)。
- 计算并记忆所有子问题的结果以“重复使用”。
Dynamic Drinking.
让我们来看看换钱问题:
我们有 3 种硬币:6 分、5 分和 1 分。兑换 9 美分最少需要多少硬币?
为了用动态规划解决这个问题,我们要做的第一件事是找到这个问题的正确递归。
我们可以看到,需要兑换 9 美分的最小硬币数是我们需要兑换 3(= 9–6)或 4(= 9–5)或 8(= 9–1)的最小硬币数。所以我们有复发:
Change Money Recurrence.
我们可以使用递归技术来实现这个问题,但它会非常慢。为什么慢?当我们想改变 76 美分时,看一看递归树:
Change 76 cents.
正如我们在可视化的树中看到的,为了计算最小数目的零钱 76 美分,我们计算最小数目的零钱 70 美分、71 美分、75 美分等等……零钱 70 的计算重复了 3 次,对于大量的零钱,我们将重复许多子问题。这要花很多时间。所以,动态规划通常喜欢“存储所有子问题的结果并重用”。这个想法是,如果我们计算改变了 70 美分,我们不需要再次计算,我们只是返回计算结果 70 美分。
实现动态编程的好方法是我们首先计算更小的子问题,将结果保存到 dp_array 中,继续直到我们得到结果。例如,要解决兑换 9 美分的问题,我们将计算我们想要兑换 0、1、2、3、4 的最小硬币数…首先,将结果保存到 dp_array 中,继续直到我们计算出 9 美分并得到答案。
让我们一步一步来:
- 计算兑换 0 美分=> 0 硬币的最小值
dp_array = [0]
2.计算最少兑换 1 美分=> 1 枚硬币
dp_array = [0,1]
3.计算最小找零 2 美分= > 2 个硬币
dp_array = [0,1,2]。
解释:2 分钱= 1 分钱+ 1 分钱。
4.计算最少兑换 3 美分=> 3 枚硬币
dp_array = [0,1,2,3]。
解释:3 分= 1 美分+ 1 美分+ 1 美分。
……
8.计算最少找 7 美分= > 2 美分
dp_array = [0,1,2,3,4,1,1,2]。
**解释:**在我们计算 6 美分的步骤,很容易发现 dp_array = [0,1,2,3,4,1,1]。
要计算 7 美分的变化,按照循环,我们需要找到(compute(6 = 7–1),compute(2 = 7–5),compute(1 = 7–6)的最小值。
我们有 dp_array 是计算小钱的缓存结果,所以我们有 compute(6) = 1,compute(2) = 2,compute(1) = 1。这些计算货币的最小值是 1。我们有两个选择:
- 1 from compute(6= 7 -1)表示我们选择 1 美分,需要 6 美分。
- 1 from compute(1 = 7–6)表示我们选择 6 美分,还需要 1 美分。
所以,我们需要 2(= 1 + 1)枚硬币将 1、5、6 美分换成 7 美分。
继续这样做,我们可以构建最终的 dp_array = [0,1,2,3,4,1,1,2,3,4]。
换 9 分钱的答案是 dp_array[9] = 4 个硬币。
总而言之,我们将首先计算更小的子问题,保存结果,在每一步,我们从保存的结果中重用子问题结果。一直做下去,直到我们得到答案。
换钱问题的伪代码:
编辑距离问题
编辑距离问题有点难以理解的问题和解决的思路。但是一旦你理解了,问题似乎就很清楚了,很容易用动态编程来解决。
你可以看到,当你输入拼写错误的东西时,拼写检查器会通过在字典中查找附近的其他单词来发出警告。什么是测量标准?换句话说,我们如何计算两个字符串之间的距离?
计算两个字符串之间距离的自然方法是扩展,对齐以匹配尽可能多的字符。例如,下雪和晴天的两种可能路线**【1】**
我们试图将下雪和晴天的字符串对齐,以获得尽可能多的匹配字符。匹配的字符越多,我们的距离就越近。如果我们把“S_NOWY”中的“”叫做插入运算符或者删除运算符(从 SUNNY 中删除“U”)和“SUNN_Y”中的“”是一样的,插入运算符到 SUNNY 或者删除运算符(在 SNOWY 中删除 W)而不匹配(O 和 N)就是不匹配运算符。那么我们可以说,将晴天转换为下雪天需要花费 3 个操作符(插入或删除,不匹配)。它们是"插入 U "、"替换 O-N "和"删除 W "。
注意:我们将使用插入操作符表示上面字符串中的“_ ”,使用删除操作符表示下面字符串中的“_”。
因此,两个字符串的距离是将字符串 A 转换为字符串 b 的插入、删除和不匹配操作符的最小数目。
计算编辑距离
为了计算 A 和 B 的编辑距离,我们需要找出正确的递归。我们有 A[1…n],B[1…m]叫做字符串 A 有 n 个字符,字符串 B 有 m 个字符。我们会找到子问题的关系:A[1…i],B[1…j]。我们有 4 种方法来对齐 A[1…i],B[1…j]。
- 插入:A[1…i, "_ "]和 B[1…j-1, B[j] ]
- 删除:A[1…i-1, A[i] 和 B[1…j, “_” ]
- 错配 A[1…i-1, A[i] 和 B[1…j-1, B[j] ],是 A[i] ≠ B[j]
- 匹配 A[1…i-1, A[i] ]和 B[1…j-1, B[j] ],就是 A[i] = B[j]
4 ways to align
要计算 A[1…i]和 B[1…j]之间的距离 D(i,j),我们需要计算 D(i,j-1),D(i-1,j),D(i-1,j-1)。
Edit distance recurrences.
因此,我们将使用二维数组来保存计算出的距离 D。
很明显,如果 D(0,j) = j 和 D(i,o) = i。
为了计算 D(1,1),我们只需要从这个计算出的距离 D 中得到值 D(i,j-1),D(i-1,j),D(i-1,j-1),意味着 D(1,0),D(0,1),D(0,0)。
那么 D(1,1) = min(2,2,1) = 1
以此类推,我们计算 D(1,2),D(1,3)…。,D(8,7)。我们有了最终计算出的距离 d。
我们看到,编辑和距离的距离是 D(7,8) = 5。
从表中,我们可以很容易地得到距离的编辑和距离是 D(4,8) = 6。每个子问题都被计算、存储以便重复使用。这是动态编程。
编辑距离的伪代码:
回溯
回溯是动态编程的一部分。我们在编辑和距离之间找到了编辑距离的答案,但是我们怎样才能打印出对齐的结果呢?
如果我们有缓存的结果——计算的距离 d,回溯就很容易。
我们将从 D(7,8)(最终结果),知道 D(7,8)是如何对齐的,我们只是检查我们如何计算 D(7,8)的值是从 D(7,7),D(6,8)和 D(6,7)。在这种情况下,D(7,8) = D(6,7) + 1 是不匹配运算符。继续这样做,我们找到最终的排列。
我们可以概括我们回溯结果的方式,要知道 D(i,j)是对齐的,我们需要检查插入操作符 - D(i,j-1) + 1、删除操作符-D(I-1,j) + 1、不匹配操作符-D(I-1,j-1)和匹配操作符 D(i-1,j-1)。D(i,j)是这些值中的一个。
回溯编辑距离的伪代码:
离散背包问题
在上一篇关于 Big-O 和 Greedy 算法的文章中,我们讨论了分数背包,也就是物品可以被分割。离散背包问题就不同了,每个物品要么拿,要么不拿。
有两种类型的离散背包:有重复和没有重复。
- 有重复:有无限的项目,你可以采取每个项目多次你想要的。
- 没有重复:每个项目都有一个,所以每个项目,你只能拿一个。
重复背包问题。
**输入:**我们有 n 个项目,权重为:w1,w2,…,wn,值为 v1,v2,…,vn。和总容量重量:w
输出:重量不超过 w 的物品的最大值每个物品可以使用任意次。
为了用动态编程解决这个问题,我们需要找出递归(子问题)并设计缓存结果。
正如我们很容易看到的,要找到项目 W(w1 + w2 + …wi)的最大值,我们需要找到(w2 + w3 + … + wi) + v1,(w1 +w3 + … + wi) + v2,……、(w1+w2+…+w(i-1)) + wi。形式上,
这就是我们需要的重现。所以我们计算 W = 1,W=2,…的最大值…直到我们得到 W = W。我们只需要一个数组 W[0…W]来保存缓存的结果。
重复背包问题的伪代码:
背包无重复问题。
**输入:**我们有 n 个项目,权重为:w1,w2,…,wn,值为 v1,v2,…,vn。和总容量重量:w
输出:重量不超过 w 的物品最大值每件物品最多使用一次。
为了找出这个问题的重复出现,我们需要澄清要点:
- 每一项要么拿,要么不拿。换句话说,W(w1,w2,…)的最大值。wi)由 W(w1,w2,…,wi)或 W(w1,w2,…)的最大值构成。w(i-1))。形式上:
value(w,i) = max{ value(w-wi,i-1) + vi , value(w,i-1) }
所以,我们需要计算(w = 1,2,…)的最大值。w, i = 1 ),(w = 1,2…w, i = 2 ),…。(w= 1,2,…w, i = n )得到结果值(w,n) 。我们需要一个二维数组来存储缓存的结果。
背包重复问题的伪代码。
例子:
我们有 4 个权重为(w1,v1)= (6,30),(w2,v2)= (3,14),(w3,v3)= (4,16),(w4,v4)= (2,9)的项目。
通过使用该策略,我们可以构建最终表:
我们可以看到,值[w=10,i=4]是 46。该值的计算公式如下
max{ value[(w=10) —(wi=2),i=2] + vi=9,value[w=10,i=2]}
= max{value[8,2] + 9,value[10,1] }
= max{ 46,30} = 46。
回溯
在我们建立了这个表之后,它包含了所有的结果值。我们可以用它回溯来标记哪个项目被使用。像上面的例子一样,值(10,4) = 46 选自值(8,2) + 9。因此,我们可以标记第 4 项未使用。
放置括号的问题。
动态规划要解决的一个好问题是括号问题。
**输入:**一串数字 d1,.。。、dn 和一系列操作
op1,…,op(n 1)∈{+,,×}。
**输出:**应用这些操作的顺序
表达式的值。D1 op1 D2 op2 op(n1)dn。
例如:
如何在一个表达式 1 + 2 — 3 x 4 — 5 中放置括号使其值最大化?
答案是:((1+2)(3×(45)))= 6。
找出这个问题的正确重现。我们假设表达式58+7×48+9的最佳括号中的最后一个操作是 x.
所以目标是找出(58+7)×(48+9)的最大值
我们现在可以看到子问题,我们有两个子表达式:s1,s2。为了找到 s1 x s2 的最大值,我们将找到 max{ min(s1) x min(s2),min(s1) x max(s2),max(s1) x min(s2),max(s1) x max(s2) }。在此基础上,我们可以计算出**(58+7)×(48+9)**的最大值
min(58+7)=(5(8+7))= 10
max(58+7)=((58)+7)= 4
min(48+9)=(4(8+9))= 13
max(48+9)=((48)+9)= 5
显然,max((58+7)×(48+9))= 130
通常,为了找到子表达式 E(i,j)的最大值,我们遵循递归:
M(i,j)是 E(i,j)的最大值,m(i,j)是 E(i,j)的最小值。
这意味着为了计算 M(i,j ),我们需要首先计算并缓存结果 M(i,k)和 M(k+1,j)。为此,我们将按照(j — i)递增的顺序计算所有子问题。换句话说,我们需要先计算(j-i)= 1,然后(j-I)= 2……例如,为了计算**5 8 + 7×4 8+9,**我们需要先计算(5 8),(8+7),(7 x 4),(4 8),(8 + 9) 子问题,然后计算 **(5 — 8 +7),(8+7 x 4),(7 x 4–8),(4–8+9)**等等……
基于该策略,我们可以构建表达式表:58+7×48+9
理解每个问题的动态编程需要时间,但是一旦你理解了它并找到了正确的递归,代码就很容易实现了。让我们通过练习一些问题来全面理解动态编程。
问题 1:原始计算器
给你一个可以用当前数 x 进行以下三种运算的原始计算器:x 乘以 2,x 乘以 3,或者 x 加 1 .给你的目标是一个正整数 n,求从数 1 开始得到数 n 所需的最小运算次数。
问题描述
**任务。**给定一个整数 n,从数字 1 开始计算得到数字 n 所需的最少运算次数。
**输出格式。**在第一行,输出从 1 得到 n 所需的最小运算次数 k。在第二行输出一系列中间数字。也就是说,第二行应该包含正整数 a0,a2,…,a(k-1)使得 a0 =1,a(k-1) =n 并且对于所有 0≤i < k-1,ai + 1 等于 ai+1,2 x ai,或者 3 x ai。如果有许多这样的序列,输出其中的任何一个。
样品 1。
输入: 5
输出:
3
1 2 4 5
解释:
这里我们先把 1 乘以 2 两次,然后再加 1((1 x 2)x 2)+1)。还有一种可能是先乘以 3,再加两次 1。因此,在这种情况下,“1 3 4 5”也是有效的输出。
样品 2:
输入: 96234
输出:
14
1 3 9 10 11 22 66 198 594 1782 5346 16038 16039 32078 96234
解释:
同样,这种情况下的另一个有效输出是“1 3 9 10 11 33 99 297 891 2673 8019 16038 16039 48117 96234”。
你的目标是为这个问题设计并实现一个动态编程解决方案。在这种情况下,一个自然的子问题如下:C(n)是从 1 得到 n 所需的最小运算数(使用三个基本运算)。如何通过 C(n/3),C(n/2),C(n-1)表示 C(n)?
解决方案:
首先要做的是找出这个问题的重现。很容易看出对于 n = 0,2,3。我们只需要一次手术。n = 1 成本 0 操作。
对于 n = 4,我们可以表示为 4 = 4/2 * 2 或 4 =(4–1)+1。
即 C(4) = C(n/2) + 1 或 C(4) = C(n-1) + 1。因为 4 不能被 3 除,所以我们忽略 C(n/3)。
通过这个观察,我们可以推广这个问题的递归性:
C(n) = min{ C(n/3) + 1 如果 n ⋮ 3 , C(n/2) + 1 如果 n ⋮ 2 , C(n-1) + 1 }
我们有递归,我们可以很容易地实现它:
在我们计算出运算的最小值后,我们需要回溯来给出数列。正如我们之前所说的,在我们计算了最少的操作之后,我们得到了缓存的结果: result 。它包含了我们从 0,1,2,… n 需要达到的所有操作数。
例如,对于计算编号 n,我们有一个操作数组:
结果= [0,0,1,1,2, 3 ,2,3,3,2, 3
如您所见,n = 10 => result[10] = 3。n = 5 = >结果[5] = 3。
为了计算结果[10],我们将基于结果[10/2] = 3 和结果[10–1]= 2。我们为前一个数字选择较小的运算。所以我们会选择 10 -1 = 9 为前面的数 10 — ( 9 -> 10)用-1 运算。
小心能除以 2 和 3 的数。因此,我们将选择除以 3,以获得更小数量的下一个 n = n/3
问题 2:尽可能多拿黄金
这个问题是关于实现无重复背包问题的算法。
问题描述
任务。在这个问题中,给你一套金条,你的目标是将尽可能多的金子放进你的包里。每个条形只有一个副本,对于每个条形,你可以选择接受或不接受(因此你不能接受条形的一部分)。
输入格式。输入的第一行包含背包的容量 W 和金条的数量 n。下一行包含 n 个整数 w0,w1,.。。w(n-1)定义金条的重量。
输出格式。输出一个容量为 W 的背包所能装下的最大重量的黄金。
样品 1。
输入:
10 3
1 4 8
输出:
9
解释:
这里我们有 W = 10,3 根金条:1,4,8。第一个和最后一个条形的权重之和等于 9。
要解决这个问题,只需仔细实现讲座中涉及的相应算法即可。
解决方案:
我们只是实现了背包,没有重复以上。但是我们需要澄清:金条的价值和金条的重量是一样的。
问题 3:计算两个字符串之间的编辑距离
两个字符串之间的编辑距离是两个字符串对齐中插入、删除和不匹配的最小数量。
问题描述
任务。这个问题的目标是实现计算两个字符串之间编辑距离的算法。
**输入格式。**两行输入的每一行都包含一个由小写拉丁字母组成的字符串。
**输出格式。**输出给定的两个字符串之间的编辑距离。
样品 1。
输入:
短裤
港口
输出:
3
说明:
总成本 3 的对齐:
要解决这个问题,只需仔细实现讲座中涉及的相应算法即可。
解决方案:
问题 4:最大化算术表达式的值
在这个问题中,你的目标是给一个给定的算术表达式加上括号,使其值最大化。
问题描述
任务。通过使用附加括号指定算术运算的应用顺序,找到算术表达式的最大值。
输入格式。输入的唯一一行包含长度为 2n + 1 的字符串 s,对于某些 n,符号为 s0,s1,.。。,s2n。s 的偶数位置上的每个符号是一个数字(即从 0 到 9 的整数),而奇数位置上的每个符号是{+、-、*}的三种运算之一。
**输出格式。**在应用算术运算的不同顺序中,输出给定算术表达式的最大可能值。
样品 2。
输入:
5–8+7*4–8+9
输出:
200
解释:
200 = (5 ((8 + 7) x (4 (8 + 9))))
要解决这个问题,只需仔细实现讲座中涉及的相应算法即可。
解决方案:
高级问题 5:三个序列的最长公共子序列
在这个问题中,你的目标是计算三个序列的最长公共子序列的长度。
问题描述
**任务。**给定三个序列 A = (a1,a2,…,an),B = (b1,b2,…,bm),C = (c1,c2,…,cl),求它们的最长公共子序列的长度。
**输入格式。**第一行:n .第二行:a1,a2,.。。,安。第三行:m .第四行:b1,b2,.。。,bm。第五行:l .第六行:c1,c2,…,cl。
**输出。**它们最长公共子序列的长度。
样品 1。
输入:
3
1 2 3
3
2 1 3
3
1 3 5
输出: 2
解释: 长度为 2 的一个常见子序列是(1,3)。
首先设计一个寻找两个(而不是三个)序列的最长公共子序列的算法可能更容易。为此,请复习课堂上计算编辑距离的算法。
解决方案:
这个问题最难的工作是找出复发。在每一步,我们如何从 D(x-1,y,z) 、 D(x,y-1,z) 和 D(x,y,z-1)之间找到 D(x,y,z) 的关系。
最后的话:
这是课程 1 —算法工具箱系列的最后一篇文章。我们有 3 种解决问题的策略:贪婪,分而治之,动态规划。我们确实讨论了 Big-O,并解决了许多关于非常大的数据集和边的问题。通过小测试很容易,但是快速、有效地通过测试却一点也不容易。
感谢您抽出时间阅读。如果你觉得不错,你可以点击♡把我的文章推荐给你的朋友。
如果你喜欢这篇文章,你可能会喜欢:
资源:
[1]:编辑距离:[DPV08]的第 6.3 节
变更问题:[CP]的“动态编程简介:变更问题”一节
背包:[DPV08]第 6.4 节
进阶阅读:【CP】第五章“我们如何比较生物序列”
进阶阅读: 高级动态编程讲义杰夫·埃里克森著
参考文献:
Sanjoy Dasgupta、Christos Papadimitriou 和 Umesh Vazirani。算法(第一版)。麦格劳-希尔高等教育。2008.
托马斯·h·科尔曼,查尔斯·e·莱瑟森,罗纳德·L·李维斯特,克利福德·斯坦。算法导论(第三版)。麻省理工学院出版社和麦格劳-希尔。2009.
菲利普·孔波夫,帕维尔·佩夫兹纳。生物信息学算法:一种主动学习方法。主动学习出版商。2014.
课程 2 —数据结构—第 1 部分:基本数据结构
algorithms + data structures version 2017
一句名言:程序=算法+数据结构。在上一个系列中,我们讲了 3 种算法贪婪、分而治之、动态规划来接近一个问题。在下一个系列中,我们将看看数据结构,以帮助我们解决更复杂的问题。让我们从最基本的数据结构开始:数组、链表。看看队列、栈是如何建立在这些数据结构之上的。什么是树?如何不用递归计算树的高度?
数组
数组是由相同大小的元素组成的连续内存区域,这些元素由连续的整数索引。
在内存中,我们只是将指针地址保存到数组中,因为所有元素在内存中是大小相等且连续的,所以要得到第 I 个元素的地址,我们只需计算:pointer _ address+element _ size * I .
基于此定义,我们可以很容易地看到阵列上的操作成本:
单链表
单链表有许多节点,每个节点包含键(数据)和指向下一个元素的指针。第一个节点叫头,最后一个节点叫尾。基于这个定义,我们可以很容易地实现单链表:
def push_front(key):
node <- new node
node.key <- key
node.next = head
head <- node
if tail == nil:
tail <- headdef pop_front():
if head == nil:
ERROR: empty list
head <- head.next
if head = nil:
tail <- nildef push_back(key):
node <- new node
node.key <- key
node.next = nil
if tail = nil:
head <- tail <- node
else:
tail.next <- node
tail <- nodedef pop_back():
if head == nil: ERROR: empty list
if head == tail:
head <- tail <- nil
else:
p <- head
while p.next.next != nil:
p <- p.next
p.next <- nil; tail <- pdef add_after(node, key):
node2 <- new node
node2.key <- key
node2.next = node.next
node.next = node2
if tail == node:
tail <- node2
双向链表
在单链表中,我们只有一个节点指向下一个元素,像 PopBack 和 AddBefore 这样的操作需要 O(n)来运行。有了双向链表,我们可以用 O(1)来运行这些操作。我们有两个节点指向下一个元素和前一个元素。
def push_back(key):
node <- new node
node.key <- key; node.next = nil
if tail = nil:
head <- tail <- node
node.prev <- nil
else:
tail.next <- node
node.prev <- tail
tail <- nodedef pop_back():
if head = nil: ERROR: empty list
if head = tail:
head <- tail <- nil
else:
tail <- tail.prev
tail.next <- nildef add_after(node, key):
node2 <- new node
node2.key <- key
node2.next <- node.next
node2.prev <- node
node.next < node2
if node2.next != nil:
node2.next.prev <- node2
if tail = node:
tail <- node2def add_before(node, key):
node2 <- new node
node2.key <- key
node2.next <- node
node2.prev <- node.prev
node.prev <- node2
if node2.prev != nill:
node2.prev.next <- node2
if head = node:
head <- node2
堆
堆栈是具有以下操作的抽象数据类型。
Push(键):将键添加到集合中。
Top():返回最近添加的键。
Pop():删除并返回最近添加的键
Empty():有没有元素?
堆栈,也称为 LIFO 队列,可以用数组或链表来实现,对于 Push、Pop、Top、Empty 操作有 O(1)。
平衡支架问题:
比方说,我们想要一个文本编辑器的检查器。我们要检查文本输入是平衡的括号如下:
平衡的:
- ([]) [] ()
- ((([([])]))())
不平衡:
- ([]]()
- ][
有了堆栈数据结构,我们可以很容易地解决这个问题:
def is_balanced(str):
Stack stack
for char in str:
if char in [ '(', '[' ]:
stack.push(char)
else:
if stack.empty(): return False
top <- stack.pop()
if (top = '[' and char != ']') or
(top = '(' and char != ')'):
return False
return stack.empty()
队列:
队列是具有以下操作的抽象数据类型:
Enqueue(key):将关键字添加到集合中。
Dequeue():移除并返回最近最少添加的键。
Empty():有没有元素?
队列,也称为 FIFO(先进先出),可以用链表(带尾指针)或数组来实现,并有 O(1)用于入队、出队和空操作。
看这些可视化:基于数组的栈,基于列表的栈,基于数组的队列,基于列表的队列。
树
树有一个根节点和许多其他节点,每个节点包含:键、子节点、父节点(可选)。对于二叉树,一个节点包含:key,left,right,parent(可选)。
这是一个二叉树的例子,我们有 Les 作为根和许多其他节点,每个节点有左和右像:Cathy(左),Sam(右)为根节点。
要计算树的高度,我们可以使用递归:
def height(tree):
if tree = nil:
return 0
return 1 + Max(height(tree.left), height(tree.right))
我们有两种方法来按特定顺序访问树的节点:
- **深度优先:**在探索兄弟子树之前,我们完全遍历子树。
def post_order_traversal(tree):
if tree = nil:
return
post_order_traversal(tree.left)
post_order_traversal(tree.right)
print(tree.key)
- **广度优先:**在前进到下一层之前,我们遍历一层的所有节点。
def level_traversal(tree):
if tree = nil: return
Queue q
q.Enqueue(tree)
while not q.empty():
node <- q.dequeue()
print(node)
if node.left != nil:
q.enqueue(node.left)
if node.left != nil:
q.enqueue(node.right)
问题 1:检查代码中的括号
在这个问题中,您将为文本编辑器实现一个功能,以查找代码中括号用法中的错误。
问题描述
任务。你的朋友正在为程序员制作一个文本编辑器。他目前正在开发一个功能,可以发现不同类型括号使用中的错误。代码可以包含集合[]{}()中的任何中括号,其中左中括号是[、{、和(对应的右中括号是]、}、和)。
为了方便起见,文本编辑器不仅应该通知用户括号的使用有错误,还应该指出有问题的括号在代码中的确切位置。首要任务是找到第一个不匹配的右括号,它要么前面没有左括号,如] in ](),要么结束了错误的左括号,如} in ()[}。如果没有这样的错误,那么它应该会找到第一个不匹配的左括号,后面没有对应的右括号,比如{}([]。如果没有错误,文本编辑器应该通知用户括号的用法是正确的。
除了括号,代码还可以包含大小拉丁字母、数字和标点符号。更正式的说法是,代码中的所有括号都应该分成匹配的括号对,这样每对括号中的开始括号都在结束括号之前,对于任何两对括号,要么其中一个嵌套在另一个中,如(foo[bar]),要么它们是分开的,如 f(a,b)-g[c]。中括号[对应于中括号],{对应于},和(对应于)。
**输入格式。**输入包含一个字符串𝑆,它由集合[]{}()中的大小拉丁字母、数字、标点符号和括号组成。
**输出格式。**如果𝑆的代码正确使用了括号,则输出“Success”(不带引号)。否则,输出第一个不匹配的右括号的从 1 开始的索引,如果没有不匹配的右括号,则输出第一个不匹配的左括号的从 1 开始的索引。
样品 1。
输入:
{}[]
输出:
Success
**解释:**括号使用正确:有两对括号——第一对{ and },第二对[and]——并且这两对不相交。
样品 2 。
输入:
{
输出:
1
**解释:**代码{没有正确使用括号,因为括号不能成对(只有一个括号)。没有右括号,第一个不匹配的左括号是{,它的位置是 1,所以我们输出 1。
样品 3。
输入:
{[}
输出:
3
**解释:**括号}不匹配,因为它前面最后一个不匹配的左括号是【and not】。它是第一个不匹配的右括号,我们的首要任务是输出第一个不匹配的右括号,它的位置是 3,所以我们输出 3。
样品 3。
输入:
foo(bar[i);
输出:
10
解释:)不匹配[,所以)是第一个不匹配的右括号,所以我们输出它的位置,是 10。
要解决这个问题,您可以稍微修改讲座中的 IsBalanced 算法,不仅考虑括号,还考虑代码中的其他字符,并且不仅返回代码是否正确使用括号,还返回代码第一个损坏的位置。
解决方案:
在讲座中,IsBalanced 伪代码为我们提供了括号的解决方案:“()[]”。我们只需要检查“{}”并跟踪我们推入堆栈的每个括号的位置。
问题 2:计算树高
树用于操作分层数据,如零售商的类别层次结构或计算机上的目录结构。它们还用于数据分析和机器学习,用于分层聚类和构建复杂的预测模型,包括一些实践中表现最好的算法,如决策树和随机森林上的梯度提升。在本课程后面的模块中,我们将介绍平衡二分搜索法树(BST),这是一种特殊的树,可以非常高效地存储、操作和检索数据。因此,平衡 BST 在数据库中用于高效存储,并且实际上在几乎任何重要的程序中使用,通常是通过手边编程语言的内置数据结构。在这个问题中,你的目标是适应树木。您需要从输入中读取树的描述,实现树数据结构,存储树并计算其高度。
问题描述
**任务。**给你一个有根树的描述。你的任务是计算并输出它的高度。回想一下,(有根的)树的高度是一个节点的最大深度,或者从一片叶子到根的最大距离。给你一棵任意的树,不一定是二叉树。
**输入格式。**第一行包含𝑛.的节点数第二行包含从 1 到𝑛1 的𝑛整数——节点的父节点。如果𝑖-th 其中之一(0≤𝑖≤𝑛1)为 1,则节点𝑖是根,否则它是𝑖-th 节点的父节点的从 0 开始的索引。保证正好有一个根。保证输入代表一棵树。
**输出格式。**输出树的高度。
样品 1。
输入:
5 4 -1 4 1 1
输出:
3
**解释:**输入表示有 5 个节点,编号从 0 到 4,节点 0 是节点 4 的子节点,节点 1 是根节点,节点 2 是节点 4 的子节点,节点 3 是节点 1 的子节点,节点 4 是节点 1 的子节点。为了看到这一点,让我们在一行中写下从 0 到 4 的节点数,并将输入中给出的数字写在下面的第二行中:
0 1 2 3 44 -1 4 1 1
现在我们可以看到,节点号 1 是根,因为 1 对应于第二行中的根。此外,我们知道节点 3 和 4 是根节点 1 的子节点。此外,我们知道节点 0 和 2 是节点 4 的子节点。
这棵树的高度是 3,因为从根 1 到叶 2 的路径上的顶点数是 3。
样品 2。
输入:
5 -1 0 4 0 3
输出:
4
解释:
输入意味着有 5 个节点,编号从 0 到 4,节点 0 是根,节点 1 是节点 0 的子节点,节点 2 是节点 4 的子节点,节点 3 是节点 0 的子节点,节点 4 是节点 3 的子节点。这棵树的高度是 4,因为从根 0 到叶 2 的路径上的节点数是 4。
为了解决这个问题,用一个适用于任意树的实现来改变课堂上描述的高度函数。请注意,在这个问题中,树可能非常深,所以如果您使用递归,应该小心避免堆栈溢出问题,并且一定要在具有最大可能高度的树上测试您的解决方案。
建议:利用每个树节点的标签都是 0 范围内的整数这一事实…𝑛−1:你可以将每个节点存储在一个数组中,数组的索引是节点的标签。通过将节点存储在一个数组中,您可以对给定标签的任何节点进行𝑂(1 访问。创建一个𝑛节点数组:
allocate 𝑛𝑜𝑑𝑒𝑠[𝑛]
for 𝑖 ← 0 to 𝑛 − 1:
𝑛𝑜𝑑𝑒𝑠[𝑖] =new 𝑁𝑜𝑑𝑒
然后,读取每个父索引:
for 𝑐ℎ𝑖𝑙𝑑_𝑖𝑛𝑑𝑒𝑥 ← 0 to 𝑛 − 1:
read 𝑝𝑎𝑟𝑒𝑛𝑡_𝑖𝑛𝑑𝑒𝑥
if 𝑝𝑎𝑟𝑒𝑛𝑡_𝑖𝑛𝑑𝑒𝑥 == −1:
𝑟𝑜𝑜𝑡 ← 𝑐ℎ𝑖𝑙𝑑_𝑖𝑛𝑑𝑒𝑥
else:
𝑛𝑜𝑑𝑒𝑠[𝑝𝑎𝑟𝑒𝑛𝑡_𝑖𝑛𝑑𝑒𝑥].𝑎𝑑𝑑𝐶ℎ𝑖𝑙𝑑(𝑛𝑜𝑑𝑒𝑠[𝑐ℎ𝑖𝑙𝑑_𝑖𝑛𝑑𝑒𝑥])
一旦你做好了树,你就需要计算它的高度。如果不使用递归,就不用担心堆栈溢出问题。如果没有递归,你将需要一些辅助数据结构来跟踪当前状态(例如,在讲座的广度优先搜索代码中,我们使用了队列)。
解决方案:
在我们构建了树之后,我们可以使用 queue 来计算树的高度。为了做到这一点,我们将使用广度优先的旅行,对于每一个水平,我们增加高度 1 多。
进阶问题 3:网络封包处理模拟
在这个问题中,你将实现一个程序来模拟网络数据包的处理
问题描述
任务。给你一系列的网络数据包,你的任务是模拟它们的处理过程。数据包以某种顺序到达。对于每个𝑖数据包,你知道它到达𝐴𝑖的时间和处理器处理它𝑃𝑖的时间(都以毫秒计)。只有一个处理器,它按照数据包到达的顺序处理传入的数据包。如果处理器开始处理某个数据包,它不会中断或停止,直到处理完这个数据包,处理数据包𝑖正好需要𝑃𝑖毫秒。
处理数据包的计算机有一个固定大小的𝑆.网络缓冲区当包到达时,它们在被处理之前被存储在缓冲器中。然而,如果当数据包到达时缓冲区已满(在此数据包之前已有𝑆数据包到达,而计算机尚未处理完其中的任何一个),它将被丢弃,根本不会被处理。如果几个数据包同时到达,它们首先都被存储在缓冲区中(其中一些可能会因此被丢弃,这些将在后面的输入中描述)。计算机按照信息包到达的顺序处理信息包,一旦处理完前一个信息包,就开始处理缓冲区中的下一个可用信息包。如果在某个时刻计算机不忙,并且缓冲区中没有数据包,计算机就等待下一个数据包到达。请注意,计算机一处理完数据包,它就会离开缓冲区并释放缓冲区中的空间。
**输入格式。**输入的第一行包含缓冲区的大小𝑆和传入网络数据包的数量𝑛。接下来的每一条𝑛线都包含两个数字。𝑖-th 线包含𝑖-th 数据包的到达时间𝐴𝑖和处理时间𝑃𝑖(均以毫秒计)。可以保证到达时间序列是不递减的(但是,它可以包含以毫秒为单位的完全相同的到达时间,在这种情况下,输入中较早的分组被认为较早到达)。
**输出格式。**对于每个数据包,输出处理器开始处理它的时刻(以毫秒为单位),如果数据包被丢弃,则输出 1(按照数据包在输入中给出的顺序输出数据包的答案)。
样品 1。
输入:
1 1 0 0
输出:
0
**解释:**唯一的数据包在时间 0 到达,计算机立即开始处理。
样品 3。
输入:
1 2 0 1 0 1
输出:
0 -1
**解释:**第一个数据包在时间 0 到达,第二个数据包也在时间 0 到达,但是被丢弃,因为网络缓冲区的大小为 1,并且它已经被第一个数据包填满。第一个数据包在时间 0 开始处理,第二个数据包根本没有处理。
样品 4。
输入:
1 2 0 1 1 1
输出:
0 1
**解释:**第一个包在时间 0 到达,计算机立即开始处理,在时间 1 结束。第二个数据包在时间 1 到达,计算机立即开始处理它。
要解决这个问题,您可以使用列表或队列(在这种情况下,队列应该允许访问它的最后一个元素,这样的队列通常称为 deque)。您可以在自己选择的语言中使用相应的内置数据结构。
一种可能的解决方案是在列表或队列 finish_time 中以递增的顺序存储计算机将完成处理当前存储在网络缓冲区中的分组的时间。当一个新的包到达时,你首先需要从 finish_time 的前面弹出所有在新的包到达时已经被处理的包。然后,您尝试在完成时间中添加新数据包的完成时间。如果缓冲区已满(在完成时间中已经有𝑆完成时间),则数据包被丢弃。否则,其加工完成时间加到完成时间上。
如果新数据包到达时完成时间为空,计算机将在新数据包到达时立即开始处理。否则,计算机将在处理完当前在 finish_time 中的最后一个数据包后立即开始处理新的数据包(此时您需要访问 finish_time 的最后一个元素,以确定计算机何时开始处理新的数据包)。您还需要通过将𝑃𝑖加到加工开始时间来计算加工完成时间,并将其推到完成时间的后面。
您需要记住输出每个数据包的处理开始时间,而不是存储在 finish_time 中的处理结束时间。
解决方案:
要解决这个问题,我们需要注意几点:
- 我们只需要记录完成时间。
- 当前处理时间是每个请求进入的时间。每次请求到达时,我们应该删除所有在完成时间队列中已经完成的请求。
- 当完成时间为空时,总是排队新请求。
阅读:
参见第 10.2 章[CLRS] —数组,链表
参见第 10.1 章[clr]—堆栈和队列
参见第 10.4 章[CLRS] —树
参考资料:
托马斯·h·科尔曼,查尔斯·e·莱瑟森,罗纳德·L·李维斯特,克利福德·斯坦。算法导论(第三版)。麻省理工学院出版社和麦格劳-希尔。2009.
课程 2 —数据结构—第 2 部分:优先级队列和不相交集
如果我们想知道后台工作是如何工作的,找到一个数组中 k 个最小元素的最快方法,数据库中的合并表是如何在后台工作的,请继续阅读。因为在本文中,我们将讨论优先级队列和不相交集。两种数据结构都很漂亮的解决了这些问题。最终,我们会解决以上这些问题。快乐阅读!
优先级队列
优先级队列是这样一种队列,其中每个元素被分配一个优先级,并且元素按优先级顺序出现。优先级队列的典型用例是调度作业。每个工作都有一个优先级,我们按照优先级递减的顺序处理工作。同时正在处理当前作业,新作业可能会到达。
优先级队列有一些主要操作:
- Insert§ :添加一个优先级为 p 的新元素。
- ExtractMax() :提取优先级最高的元素。
- ChangePriority(it,p) :将 it 指向的元素的优先级改为 p 。
优先级队列用于许多算法中:
- Dijkstra 算法:在图中寻找最短路径。
- Prim 的算法:构造一个图的最小生成树。
- 霍夫曼算法:构造一个字符串的最佳无前缀编码。
- 堆排序:对给定的序列进行排序。
您可以使用未排序/已排序的数组或列表来实现优先级,但是每一种都有一个权衡:
但是通过使用二进制堆,我们可以用 O(logn)做 Insert ,用 O(logn)做 ExtractMax 。
二元堆
二进制堆有两种类型:二进制最小堆和二进制最大堆。在本文中,我们将讨论二进制最大堆。另一方面,二进制最小堆也有相同的实现方式。
二进制最大堆是一个二叉树(每个节点有零个、一个或两个孩子),其中每个节点的值至少是其孩子的值。
如您所见,每个子节点的值都不大于父节点的值。根节点具有最大值。
Not a max-heap. Some children are bigger than parent.
Max-Heap 有一些基本操作:
- GetMax :返回根值即可。成本 O(1)。
- 插入:我们将把一个新节点附加到任何一个叶子上。如果它违反了堆属性,我们将冒泡(称为 SiftUp )新节点,直到满足堆属性。o(树高)
如您所见,我们将新节点 32 连接到节点 7,并将其冒泡到节点 29。
- ExtractMax :根节点 R 是 max-heap 的最大值。要弹出根节点,我们只要把根节点 R 换成任意一个叶节点 A,去掉节点 R 就可以了,可能会违背 heap 的属性。我们将做 SiftDown 操作。作为父节点 A,我们要 SiftDown 到子节点 B 和 C,我们会选择子节点 B 和 C 的最大值,交换到节点 A,我们 SiftDown 直到满足堆的属性。
我们首先交换根节点 42 和随机叶节点 12,并移除节点 42。现在违反了最大堆,我们需要将节点 12 向下筛选为子节点。在最后一步,节点 29 是根节点。
- 改变优先级:我们需要改变一个节点的优先级。根据新优先级的值,我们将使用 SiftDown 或 SiftUp 来限定堆属性。
- 移除:移除节点 a。我们分两步完成:
步骤 1: 使节点 A 值变得最大(无穷大)。我们将执行 SiftUp 操作,使节点 A 成为根节点。
**第二步:**我们执行上面描述的 ExtractMax 操作。
建筑堆
我们知道堆是如何工作的,我们需要找到建立一个堆的方法。为了构建一个堆并保持一个二叉堆树浅滩,我们必须在最后一层从左到右填充新的节点。叫做完全二叉树。形式上:
如果一棵二叉树的所有层次都被填满,除了可能是从左到右填满的最后一层,那么它就是完整的。所以一棵有 n 个节点的完全二叉树的高度最多是 O(logn)。
一个完整的二叉树有从左到右填充的元素,所以我们可以将树存储为一个数组,其中
因为我们以数组的形式存储,所以我们执行的每个操作都需要保持树的完整性。
最大堆的伪代码
# we store elements in array Hdef parent(i):
return i/2def left_child(i):
return 2idef right_child(i):
return 2i + 1def sift_up(i):
while i > 1 and H[parent(i)] < H[i]:
swap H[parent(i)] and H[i]
i <- parent(i)def sift_down(i):
maxIndex <- i
left <- left_child(i)
if left <= size and H[left] > H[maxIndex]:
maxIndex <- left
right <- right_child(i)
if right <= size and H[right] > H[maxIndex]:
maxIndex <- right
if i != maxIndex:
swap H[i] and H[maxIndex]
SiftDown(maxIndex)def insert(p):
if size = maxSize:
return ERROR
size <- size + 1
H[size] <- p
SiftUp(size)def extract_max():
result <- H[1]
H[1] <- H[size]
size <- size - 1
SiftDown(1)
return resultdef remove(i):
H[i] = ∞
sift_up(i)
extract_max()def change_priority(i, p):
oldp <- H[i]
H[i] <- p
if p > oldp:
sift_up(i)
else:
sift_down(i)
堆排序
回到排序问题,我们观察到最大堆的根节点是最大值。如果我们做 ExtractMax() 并放入另一个数组,结果将是排序递减数组。这是选择排序的基础。它的价格为 O(nlogn)。
def heap_sort_selection_sort(A[1...n]):
create an empty priority queue
for i from 1 to n:
Insert(A[i]) # Insert operation to build max-heap.
for i from n downto 1:
A[i] <- ExtractMax()
事实上,我们可以做更好的堆排序算法,不使用任何额外的数组,它是就地堆排序算法。给定一个数组
A = [ 4, 1, 3, 2, 16, 9, 10, 14 , 8, 7]
我们将数组 A 表示为一个堆:
现在我们需要修复所有节点以满足 max-heap 的属性。我们可以开始修复深度为 1 的所有子树中的节点(深度为 0 的是所有的叶子)。深度为 1 的节点从 n/2 到 1
def build_heap(A[1...n]):
size <- n
for i from n/2 downto 1:
SiftDown(i)
我们从父节点 5,4,3,2,1([16,2,3,1,4])开始。每一步,我们做 SiftDown 操作。最后,我们可以有一个最大堆(f)。
要对最大堆进行排序,我们只需交换节点 A[1]和 A[size],移除 A[size]和 SiftDown(1)。
def heap_sort(A[1...n]):
repeat (n-1) times:
swap A[1] and A[size]
size <- size - 1
SiftDown(1)
部分排序
给定数组 A[1…n],并且 1 ≤ k ≤ n。输出 A 的排序版本的最后 k 个元素。
有了堆排序,我们很容易解决这个问题:
def partial_sorting(A[1...n], k):
build_heap(A)
for i from 1 to k:
extract_max()
运行时间为:O(n+klogn)。对于较小的数 k = O(n/logn),代价将为 O(n)。真令人印象深刻。
不相交集
不相交集有许多应用,其中之一是确定无向图的连通分量。
不相交集合数据结构维护不相交动态集合的 S1、S2、…、Sk 的集合。我们把每个集合表示成一棵有根的树。
我们可以通过使用一个数组来存储这些集合(有根的树),该数组的值 A[i]是节点 I 的父节点,或者如果节点 I 是根节点,则它是节点 I 的父节点。
不相交集有一些主要操作:
- MakeSet(i) :创建一个只包含一个 I 集合:{i}。
def make_set(i):
parent[i] <- i
- 查找(i) :在一个集合中查找 I,返回根索引
def find(i):
while i != parent[i]:
i <- parent[i]
return i
- 联合(S2 S1):合并两个有根的树。这是不交集最重要的运算。
我们有两个有根的树,我们想联合这些树。通常,我们有两种方法可以做到这一点:
好的那个是身高较矮的那个。我们如何知道为并集选择根节点?
为了回答这个问题,我们只需要使用一个等级作为高度来选择最终的根。换句话说,我们只是把矮的挂在高的根部。为了避免每次执行 union 操作时都要重新计算 rank(height ),我们需要将每个子树的高度存储在数组 rank[1…n] 中,其中 rank[i]是根为 I 的子树的高度。
我们改变了不相交集合运算的实现:
def make_set(i):
parent[i] <- i
rank[i] <- 0def find(i):
while i != parent[i]:
i <- parent[i]
return idef union(i, j):
i_id <- find(i)
j_id <- find(j)
if i_id == j_id:
return
if rank[i_id] > rank[j_id]:
parent[j_id] <- i_id
else:
parent[i_id] <- j_id
if rank[i_id] == rank[j_id]:
rank[j_id] <- rank[j_id] + 1
让我们把构建不相交集合的过程形象化,做并集(2,4),并集(5,2),并集(3,1),并集(2,3),并集(2,6)。
每一步,我们需要选择较低等级的树,并将其附加到较高等级的树。当我们有两个相同等级的树时,我们只增加结果的等级。
通路压缩
在造树的时候,我们可以马上意识到我们的树会变得越来越高。让我们看看如何找到元素 6,我们确实遍历了所有父节点以得到根节点 5。
请注意,所有节点 6、12、3 都有相同的根节点 5,因此,如果我们能够将它们的所有父节点转换为直接父节点 5,就太好了,就像这样:
Path Compression
你可以看到,我们可以压缩高度,我们可以更快地找到元素。你会惊奇地发现,这种启发式的实现如此简单:
def find(i):
if i != parent[i]:
parent[i] <- find(parent[i])
return parent[i]
问题 1:将数组转换成堆
在这个问题中,你将把一个整数数组转换成一个堆。这是称为 HeapSort 的排序算法的关键步骤。它保证了最坏情况下𝑂(𝑛日志𝑛的运行时间,而不是快速排序的𝑂(𝑛日志𝑛).的平均运行时间快速排序通常在实践中使用,因为通常它更快,但是堆排序用于外部排序,当你需要排序不在你的计算机内存中的大文件时。
问题描述
**任务。**heap sort 算法的第一步是从想要排序的数组中创建一个堆。顺便说一句,你知道基于堆的算法被广泛用于外部排序吗?当你需要对不在计算机内存中的大文件进行排序时。
您的任务是实现这第一步,并将给定的整数数组转换成堆。为此,您需要对阵列应用一定数量的交换。Swap 是将数组𝑎中的元素𝑎𝑖和𝑎𝑗与一些𝑖和𝑗.进行交换的操作你需要将数组转换成一个只使用𝑂(𝑛交换的堆,正如在课堂上所描述的。请注意,在这个问题中,您需要使用最小堆而不是最大堆。
**输入格式。**输入的第一行包含单个整数𝑛.下一行包含𝑛空格分隔的整数𝑎𝑖.
**输出格式。**输出的第一行应该包含单整数𝑚——交换的总数。𝑚必须满足条件 0 ≤ 𝑚 ≤ 4𝑛.接下来的𝑚行应该包含用于将数组𝑎转换为堆的交换操作。每次交换都由一对整数𝑖,𝑗来描述,这是要交换的元素的从 0 开始的索引。在按指定顺序应用所有交换后,数组必须变成一个堆,也就是说,对于 0≤𝑖≤𝑛1 的每个𝑖,必须满足以下条件:
1\. If 2𝑖+1 ≤ 𝑛−1,then 𝑎𝑖 < 𝑎(2𝑖+1).
2\. If 2𝑖+2 ≤ 𝑛−1,then 𝑎𝑖 < 𝑎(2𝑖+2).
请注意,输入数组的所有元素都是不同的。请注意,任何长度不超过 4𝑛的交换序列,在此之后,您的初始数组成为正确的堆,都将被评定为正确。
样品 1。
输入:
5
5 4 3 2 1
输出:
3
1 4
0 1
1 3
说明:
After swapping elements 4 in position 1 and 1 in position 4 the array becomes 5 1 3 2 4.After swapping elements 5 in position 0 and 1 in position 1 the array becomes 1 5 3 2 4.After swapping elements 5 in position 1 and 2 in position 3 the array becomes 1 2 3 5 4, which is already a heap, because 𝑎0 =1 < 2 = 𝑎1, 𝑎0 =1 < 3=𝑎2, 𝑎1=2 < 5=𝑎3, 𝑎1=2 < 4=𝑎4.
更改讲座中的 BuildHeap 算法,以考虑最小堆而不是最大堆,并考虑基于 0 的索引。
解决方案:
在构建堆时,我们将从第 n/2 个节点向下到第 1 个节点进行 SiftDown 操作,以修复堆来满足最小堆属性。每次交换节点时,我们都需要跟踪它。
问题 2:并行处理
在这个问题中,您将模拟一个并行处理一系列作业的程序。像 Linux、MacOS 或 Windows 这样的操作系统都有特殊的程序,叫做调度程序,它对你计算机上的程序做同样的事情。
问题描述
**任务。**您有一个并行化的程序,它使用𝑛独立线程来处理给定的𝑚作业列表。线程按照输入中给出的顺序接受任务。如果有空闲线程,它会立即从列表中获取下一个任务。如果一个线程已经开始处理一个作业,它不会中断或停止,直到它完成处理该作业。如果几个线程试图同时从列表中获取作业,索引较小的线程会获取作业。对于每一个任务,你确切地知道任何一个线程处理这个任务需要多长时间,这个时间对于所有的线程都是一样的。您需要为每个作业确定哪个线程将处理它,以及它何时开始处理。
**输入格式。**输入的第一行包含整数𝑛和𝑚.
第二行包含𝑚整数𝑡𝑖——任何线程处理𝑖-th 作业所用的时间(秒)。时间的顺序与它们在线程获取作业的列表中的顺序相同。
线程从 0 开始进行索引。
**输出格式。**准确输出𝑚线。𝑖-th 行(使用基于 0 的索引)应该包含两个用空格分隔的整数——将处理𝑖-th 作业的线程的基于 0 的索引和它将开始处理该作业的时间(以秒为单位)。
样品 1。
输入:
2 5 1 2 3 4 5
输出:
0 01 00 11 20 4
说明:
1\. The two threads try to simultaneously take jobs from the list, so thread with index 0 actually takes the first job and starts working on it at the moment 0.2\. The thread with index 1 takes the second job and starts working on it also at the moment 0.3\. After 1 second, thread 0 is done with the first job and takes the third job from the list, and starts processing it immediately at time 1.4\. One second later, thread 1 is done with the second job and takes the fourth job from the list, and starts processing it immediately at time 2.5\. Finally, after 2 more seconds, thread 0 is done with the third job and takes the fifth job from the list, and starts processing it immediately at time 4.
想想当其中一个线程空闲时的事件顺序(在开始时和完成某个任务后)。如何应用优先级队列来模拟按要求的顺序处理这些事件?记得考虑几个线程同时空闲的情况。
在这个问题中要小心 integer over ow:在 C++中使用 long long 类型,在 Java 中使用 long 类型,只要正则类型 int 能够覆盖 ow,就要考虑问题语句中的限制。
解决方案:
这个想法是我们需要一个优先队列来留住工人。我们通过状态和指数比较两个工人。要选择下一个工人,我们只需选择根最小堆树。为了让它工作,我们需要改变根树和修复树的优先级。
问题 3:合并表格
在这个问题中,您的目标是模拟数据库中表的一系列合并操作。
问题描述
任务。在某个数据库中存储有𝑛表。这些桌子从 1 到𝑛.编号所有表共享同一组列。每个表包含几行实际数据或指向另一个表的符号链接。最初,所有表都包含数据,𝑖-th 表包含𝑟𝑖行。您需要执行以下操作的𝑚:
1.考虑𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖).号桌遍历符号链接的路径以获取数据。也就是说,虽然𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖)包含了象征性的联系而不是真实的数据 do𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖)←symlink(𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖))
2.考虑表格编号𝑠𝑜𝑢𝑟𝑐𝑒(𝑖)并从它开始以与𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖).相同的方式遍历符号链接的路径
3.现在,𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛𝑖和𝑠𝑜𝑢𝑟𝑐𝑒(𝑖)是有真实数据的两个表的数字。如果𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖)!= 𝑠𝑜𝑢𝑟𝑐𝑒(𝑖),将𝑠𝑜𝑢𝑟𝑐𝑒(𝑖表中的所有行复制到𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖表中,然后清空𝑠𝑜𝑢𝑟𝑐𝑒(𝑖表,并在其中放置一个指向𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖的符号链接,而不是真正的数据。
4.打印所有𝑛表中的最大大小(回想一下,大小是表中的行数)。如果表格只包含一个符号链接,则其大小被认为是 0。
请参见示例和解释以获得进一步的说明。
输入格式。输入的第一行包含两个整数𝑛和𝑚——分别是数据库中表的数量和要执行的合并查询的数量。
输入的第二行包含𝑛整数𝑟𝑖——𝑖-th 表中的行数。
然后按照描述合并查询的𝑚线。每个都包含两个整数𝑑𝑒𝑠𝑡𝑖𝑛𝑎𝑡𝑖𝑜𝑛(𝑖和𝑠𝑜𝑢𝑟𝑐𝑒(𝑖)——要合并的表的编号。
**输出格式。**对于每个查询,打印一行包含单个整数的内容——相应操作后所有表的最大大小(根据行数)。
样品 1。
输入:
5 5
1 1 1 1 1
3 5
2 4
1 4
5 4
5 3
输出:
2
2
3
5
5
说明:
在这个示例中,所有的表最初都只有一行数据。考虑合并操作:
1.表 5 中的所有数据都被复制到表 3 中。表 5 现在只包含一个到表 3 的符号链接,而表 3 有 2 行。2 成为新的最大尺寸。
2.2 和 4 的合并方式与 3 和 5 相同。
3.我们正在尝试合并 1 和 4,但是 4 有一个指向 2 的符号链接,所以我们实际上是将 2 号表中的所有数据复制到 1 号表中,清除 2 号表并在其中放一个指向 1 号表的符号链接。表 1 现在有 3 行数据,3 成为新的最大大小。
4.从 4 开始遍历符号链接的路径我们有 4→2→1,从 5 开始的路径是 5→3。所以我们实际上合并了表 3 和表 1。我们将 1 号表中的所有行复制到 3 号表中,现在 3 号表有 5 行数据,这是新的最大值。
5.现在所有的表都直接或间接地指向表 3,所以所有其他的合并不会改变任何东西。
样品 2。
输入:
6 4
10 0 5 0 3 3
6 6
6 5
5 6
4 3
输出:
10
10
10
11
解释:
在这个例子中,表格具有不同的大小。让我们考虑一下操作:
1.将 6 号表与其自身合并不会改变任何东西,最大大小是 10(1 号表)。
2.在将表号 5 合并到表号 6 之后,表号 5 被清除并且大小为 0,而表号 6 的大小为 6。不过,最大尺寸是 10。
3.通过将 4 号表合并到 5 号表,我们实际上将 4 号表合并到 6 号表(现在 5 号表只包含一个到 6 号表的符号链接),所以 4 号表被清除,大小为 0,而 6 号表的大小为 6。不过,最大尺寸是 10。
4.通过将 3 号表合并到 4 号表,我们实际上将 3 号表合并到 6 号表(4 号表现在只包含一个到 6 号表的符号链接),所以 3 号表被清除,大小为 0,而 6 号表的大小为 11,这是新的最大大小。
思考如何使用带路径压缩的不相交集合并和按等级启发式的并来解决这个问题。特别是,您应该将执行 union/find 操作的数据结构从表的合并中分离出来。如果要求您将第一个表合并到第二个表中,但是第二个表的等级小于第一个表的等级,则在不相交集合联合数据结构中进行合并时,您可以忽略所请求的顺序,而是在不相交集合联合中将对应于第二个表的节点连接到对应于第一个表的节点。但是,您将需要存储实际的第二个表的编号,您被请求将第一个表合并到相应不相交集的父节点中,并且您将需要在不相交集联合的节点中附加一个 eld 来存储它。
解决方案:
我们使用 父 数组来存储不相交集合。
我们用 行 数组来存储根 I 的最大值
我们需要用耙集和路径压缩启发式建立不相交集。
阅读材料:
参见[CLRS] —优先级队列中的第 6.4 章
参见此最小堆可视化。
参见[DPVo8] —不相交集的第 5.1.4 节
另请参见本教程中关于不相交集合数据结构的内容。
另请参见这个不相交集合的可视化,有和没有路径压缩和按等级试探法联合。
参考文献:
Sanjoy Dasgupta、Christos Papadimitriou 和 Umesh Vazirani。算法(第一版)。麦格劳-希尔高等教育。2008.
托马斯·h·科尔曼,查尔斯·e·莱瑟森,罗纳德·L·李维斯特,克利福德·斯坦。算法导论(第三版)。麻省理工学院出版社和麦格劳-希尔。2009.
课程回顾:机器学习专业化的数学
机器学习的重要基础之一是数学。不懂机器学习数学的人永远不会理解各种 python/R API 底层上的概念。
当我第一次潜入机器学习的海洋时,我在 Coursera 上选择了吴恩达教授的机器学习课程。他是这个领域的优秀教师,有着多年的经验。
因为他在教机器学习,我想说从很久以前,他就已经很好地理解和学习了他的概念。但是,作为初学者和职场人士,我们是否已经很好地理解和学习了我们的概念?我会说不。大多数情况下,职场人士会忘记一些基本概念。
我在大学的时候对这些有相当的理解,但是那些知识已经消失了。不出所料,随着斯坦福 ML 课程的进展,我度过了一段艰难的时光,尤其是一些数学推导。为了让您更好地了解本课程的内容,每个子模块持续一周,但同一主题可以有自己的课程,时间跨度为 3-4 周。
于是我在第 6 周停了下来,开始上这门课:伦敦帝国理工学院机器学习专精数学。
我们来复习一下这门课。
该专业有三门课程,即。线性代数、多元微积分和主成分分析。这些课程的时间跨度为 4-6 周。
- 线性代数
这门课程讲的是解决机器学习问题所需的向量以及与向量相关的各种重要概念。正如老师自己所说,这门课不是要详细理解矢量,并掌握矢量。但是这门课是为了有足够的关于向量的知识,能够利用向量来解决机器学习的问题。
讲师从什么是向量开始,到点积、点积直觉、基向量,改变基向量直到特征值和特征向量。本课程试图解决的第二部分是向量的矩阵表示、矩阵运算以及如何将基本向量的概念应用于矩阵。
本课程有著名的谷歌页面排序算法练习,该练习展示了向量和矩阵对该领域的重要性。 - 多元微积分
顾名思义,这门课讲的是单变量微分,多变量微分,链式法则,雅可比行列式,黑森拉格朗日乘数,泰勒级数。这门课很容易理解,因为许多学生已经将这些概念作为本科学习要求的一部分。
本课程有助于培养这些概念对高维度图形的直觉,以及如何使用它们找到最佳解决方案。
许多学生可能一直想知道这些概念在未来生活中的应用。如果你是有抱负的数据科学家,本课程将帮助你回答这些问题。 - 主成分分析
我在理解这门课程时面临困难,但在看了 loop 上的视频和在讨论论坛上冲浪后,我扫清了所有的障碍。这门课很难,尽管与其他两门课相比内容较少。
无论前两门课程中提到了什么概念,在这里都可以用来理解机器学习的一个重要概念:主成分分析。本课程要求你事先具备 Python 库 numPy 的知识。
从一点点统计开始,向量之间的点和正交性,矩阵中的投影,多元微积分概念。这可能感觉像是前两门课程中的一门,但是随着课程的进展,它会变得有点困难,因为你需要解决编程作业。如果你不熟悉 numPy 和其他 python 库,你可能会在编程任务中遇到麻烦。我发现这两者之间令人困惑,但论坛讨论将会来拯救,只要你有毅力筛选它们。
我的拍摄:
总的来说,课程非常棒。我感觉从机器学习开始所需要的数学概念都已经涵盖了。
我认为我很有资格欣赏课程内容,因为我知道这些概念在机器学习中的应用,而不是初学者。当我理解这门课中的一些推导/概念时,我确实有了一些顿悟。当我在斯坦福学习机器学习课程时,我对推导有一些疑问,我觉得这门课很好地澄清了这些疑问。
测验让你对所学的概念有更深入的了解,并期望你用跳出框框的思维来解决问题。
虽然有几次我不得不参考其他资源,如 youtube 等,来理解这些概念。然而,我不认为这降低了这门课程的魅力。
彻底总是好的——不同的来源提供不同的看法,并对主题提供更多的见解。如果没有别的,它们可以作为一个很好的修订。
结论:
这个课程对一个刚开始学习机器数学的初学者来说非常好。我会给它 4.5/5 颗星。
我建议你学完这门课程后,也参考一下其他资料。
Coursera 机器学习评论
Aadam — Machine Learning Certificate
我一直想学完这门课,但每次都有其他吸引我的东西,比如其他 MOOC 或主题。我知道,由于受欢迎程度和众多积极的评论,这门课将会是一门好课,但每次都会有一个新的 MOOC 出现在一些我一直在等待的新主题上,我会冲向它。
但是现在,吴恩达发布了一个关于深度学习的新专业,我渴望接下来开始这个专业,我有足够的动力完成这个课程,因为我认为这将被证明是一个无价的资源,也是深度学习专业将涵盖的主题的坚实基础。
我惊讶地发现,这门课比我想象的要有收获得多。所有的材料都经过精心挑选,并有直观的解释。吴恩达是我有幸学习过的最好的老师之一。他如此简单直观地解释困难而复杂的概念的方式是非凡的。很多以前很难理解的话题,听了安德鲁的解释后,似乎变得很容易理解,我想知道为什么我以前没有理解。
这是给谁的?
这个课程值得我浪费 11 周的时间吗?我应该期望从这门课程中得到什么?
如果你认真学习机器学习,并希望深入了解算法的内部工作,那么这门课程非常适合你。这门课程比今天提供的大多数机器学习课程要古老得多,但它在解释基本概念方面做得很好,以便更好地理解当今使用的机器学习算法。
如果你只想对机器学习如何工作有一个大致的了解,并学习一些库或工具来执行基本的机器学习任务,那么你最好使用一些其他资源,例如 Udacity 的机器学习工程师 Nanodegree 。
并不是说这门课不好或者不会让你受益,只是它的受众人群不同而已。它迎合更有技术头脑的人。它大量使用数学来解释算法。你需要对线性代数和微积分有一个基本的了解,以便对算法的内部工作有一个直觉。所有的作业都在 MATLAB 中,不像大多数新的机器学习课程使用 Python。
我强烈推荐这门课程,因为它对我非常有益。我学到了很多新东西,最重要的是,它消除了我对一些学习算法的许多困惑和误解。我知道如何使用这些算法,但我不明白它们是如何工作的。就像使用魔咒一样,我复制了别人说会起作用的东西,却不明白为什么会起作用,这让我很沮丧。这门课程让我明白了很多事情。现在,我将能够使用机器学习技术和算法,知道这是为什么和如何工作的,这将对我试验新技术和测试新想法有很大帮助。
我仍然对一些算法感到困惑,更具体地说是反向传播算法,但在我看来,根据吴恩达的说法,它是机器学习中最复杂的算法之一。我凭直觉理解反向传播的工作方式和原因,但我对算法背后的数学原理有点困惑。我想我得再看一遍视频,一两遍。
我已经在 Cousera 上开始了由吴恩达提供的深度学习专业。我上了神经网络和深度学习的最后一周,这是专业化的第一门课程。在接下来的帖子里,我会告诉你我在这个课程上的经历,安拉保佑。
人工智能的表亲
人工智能、机器学习和深度学习是全球的热门话题,其应用部署在所有主要的业务领域。这些术语让很多人感到困惑,并且经常互换使用。如果你是其中之一,那么让我们试着理解人工智能和它的兄弟姐妹的关系。
人工智能是机器学习(ML)和深度学习(DL)的更广泛的保护伞。图表显示,ML 是 AI 的子集,DL 是 ML 的子集。
Cousins of AI
人工智能
“通过计算机程序对人类心理功能建模的研究。”——柯林斯字典
人工智能由人工和智能两个词组成。任何非自然的和人类创造的东西都是人造的。智力意味着理解、推理、计划等能力。因此,我们可以说,任何使机器能够模仿、发展或展示人类认知或行为的代码、技术或算法都是人工智能。
人工智能的概念很古老,但最近开始流行。但是为什么呢?
原因是之前我们只有非常少量的数据来做出准确的预测。但是今天,每分钟产生的数据量都在急剧增加,这有助于我们做出更准确的预测。除了巨大的数据量,我们还有更先进的算法、高端计算能力和存储的支持,可以处理如此巨大的数据量。例子包括特斯拉自动驾驶汽车,苹果的 Siri 等等。
机器学习
我们已经看到了什么是人工智能,但是是什么问题导致了机器学习的引入?
几个原因是:
在统计学领域,问题是“如何有效地训练大型复杂模型?”在计算机科学和人工智能领域,问题是“如何训练更健壮的人工智能系统?
因为这些问题,机器学习被引入。
什么是机器学习?
“机器学习是让计算机在没有明确编程的情况下行动的科学.”——斯坦福大学
它是人工智能的一个子集,使用统计方法使机器能够随着经验而改进。它使计算机能够采取行动并做出数据驱动的决策来执行某项任务。这些程序或算法的设计方式使得它们可以在接触新数据时随着时间的推移而学习和改进。
例如:
假设我们想创建一个系统,根据身高告诉我们人的预期体重。首先,我们将收集数据。这就是数据的样子(下图)。图上的每个点代表一个数据点。
首先,我们将根据身高画一条简单的线来预测体重。
一条简单的线可以是 W=H-100
在哪里
w =以千克为单位的重量
h =以厘米为单位的高度
这条线可以帮助我们做预测。我们的主要目标是缩小估计值和实际值之间的距离。即误差。为了实现这一点,将绘制一条穿过所有点的直线。
我们的主要目标是最小化误差,使它们尽可能小。减少实际值和估计值之间的误差可以提高模型的性能,而且我们收集的数据点越多,我们的模型就越好。
因此,当我们输入一个人的身高的新数据时,它可以很容易地告诉我们这个人的体重。
深度学习
“深度学习是机器学习的一个子领域,涉及被称为人工神经网络的大脑结构和功能所启发的算法”。——机器学习掌握
这是一种特殊的机器学习,它受到我们称为神经元的脑细胞的功能的启发,这导致了人工神经网络(ANN)的概念。使用人工神经元或计算单元的层来模拟 ANN,以接收输入并应用激活函数和阈值。
在简单模型中,第一层是输入层,接着是隐藏层,最后是输出层。每层包含一个或多个神经元。
Layers in Artificial neural Network
用简单的例子来理解概念层面的事情是如何发生的
你如何从其他形状中识别正方形?
我们要做的第一件事是检查图形是否有四条线。如果是,我们进一步检查是否所有线路都已连接和关闭。如果是,我们最后检查是否所有的都是垂直的,所有的边都是相等的。
如果图形满足所有条件,我们就认为它是正方形。
正如我们在示例中看到的,它只是概念的嵌套层次。所以我们接受了一个复杂的识别正方形的任务,并把它分解成几个简单的任务。深度学习也做同样的事情,但规模更大。
例如,机器执行识别动物的任务。机器的任务是识别给定图像是猫还是狗。
Differentiating between dog & cat using Deep Learning algorithm
如果我们要求我们使用概念机器学习来解决这个问题,那么我们会定义一些特征,例如检查它是否有胡须,检查尾巴是直的还是弯曲的,以及许多其他特征。我们将定义所有特征,并让我们的系统识别哪些特征在对特定动物进行分类时更重要。现在,当涉及到深度学习时,它向前迈出了一步。与我们必须手动给出特征的机器学习相比,深度学习会自动找到哪些特征对分类最重要。
所以到目前为止,我们知道人工智能是一个更大的图景,机器学习和深度学习是它的子部分。
机器学习(ML) vs 深度学习(DL)
理解机器学习和深度学习之间的区别的最简单的方法是“DL 是 ML”。更具体地说,这是机器学习的下一次进化。
我们取几个重要参数,对比一下机器学习和深度学习。
- 数据依赖
两者之间最重要的区别是随着数据大小的增加,性能会有所提高。从下图中我们可以看到,由于数据的大小很小,深度学习的表现并不好,但为什么呢?
这是因为深度学习算法需要大量数据才能完美理解。另一方面,机器学习在较小的数据集上工作得非常好。
2。硬件依赖性
深度学习算法高度依赖高端机器,而机器学习算法也可以在低端机器上工作。这是因为深度学习算法的要求包括 GPU,这是其工作不可或缺的一部分。GPU 是必需的,因为它们执行大量的矩阵乘法运算,这些运算只有在使用 GPU 时才能得到有效优化。
3。特征工程
它是将领域知识用于降低数据复杂性并使模式对学习算法更可见的过程。就时间和专业知识而言,这一过程既困难又昂贵。在机器学习的情况下,大多数特征需要由专家识别,然后根据域和数据类型进行手工编码。机器学习的性能取决于如何准确地识别和提取特征。但是在深度学习中,它试图从数据中学习高层次的特征,因此它领先于机器学习。
4。问题解决方法
当我们使用机器学习解决问题时,建议首先将问题分解成子部分,单独解决它们,然后将它们组合起来以获得最终结果。另一方面,在深度学习中,它端到端地解决问题。
举个例子,
任务是多目标检测,即目标是什么以及它出现在图像中的什么位置。
所以让我们看看如何使用机器学习和深度学习来解决这个问题。
在机器学习方法中,我们将问题分成两部分。物体检测和物体识别。
我们将使用像包围盒检测这样的算法作为例子来扫描整个图像并检测所有对象,然后使用对象识别算法来识别相关对象。当我们结合两种算法的结果时,我们将得到最终结果,即什么是对象以及它在图像中的位置。
在深度学习中,它从头到尾执行这个过程。我们将把一幅图像传递给一个算法,我们的算法将给出物体的位置和名称。
5。执行时间
深度学习算法需要大量的时间来训练。这是因为深度学习算法中有太多的参数,需要比平时更长的训练时间。而在机器学习中,与深度学习相比,训练时间相对较少。
现在,在测试数据时,执行时间完全相反。在测试过程中,深度学习算法的运行时间非常短,而 KNN 等机器学习算法的测试时间会随着数据量的增加而增加。
6。可解释性
这是人们在行业内使用之前想了很多的主要原因。假设我们用深度学习给出自动化的作文评分。它的表现非常出色,与人类一样,但有些问题它没有告诉我们为什么它会给出这样的分数,事实上,从数学上来说,我们可以找出当时深层神经网络的哪些节点被激活,但我们不知道神经元应该模拟什么,以及这些层集体在做什么。所以我们无法解释结果,但在机器学习算法中,如决策树,给了我们一个清晰的规则,为什么它选择了它选择的东西,所以很容易解释背后的推理。
我希望现在你已经对这三者有了清晰的认识,他们之间有什么样的关系,他们之间有什么不同。
感谢阅读!
封面字母+数据科学=你需要知道的
超过字母是最糟糕的。它们揭示了候选人的弱点,雇主不喜欢阅读它们。如果一个申请系统要求你为一个你正在寻找的新职位写一封求职信,或者,你听说你必须在简历中附上一封求职信,这篇文章就是为你准备的。
在继续之前,让我们先关注一些四处流传的求职信建议。大多数求职信都有一个非常典型的结构:介绍你是谁;讨论你的经历;并解释你如何成为这份工作的合适人选。本质上,这是最基本的求职信,或者我所说的基本求职信格式。
这看起来很简单,但是大多数人都搞砸了,那些做对的人也没好到哪里去。想想看:如果几乎每个申请职位的人都遵循基本的求职信格式(因为他们都参加了相同的职业研讨会,浏览了相同的网站,并向相同的“职业顾问”寻求建议),那么求职信就不会引人注目——推而广之,他们就成了一个普通的候选人。
求职信总是在看简历之前被阅读。如果你给人的感觉是稳重和疲惫,那么这就是你简历中的感觉。所以,在坐下来写你的求职信之前,问问你自己——我希望招聘经理在阅读我的简历之前是什么样的心情?我们大多数人会说好奇、印象深刻、好奇等等。这是你的求职信应该做的,但是很多人没有做到。
少写,不多写
许多工程师、数据科学家、统计学家和数学家回避写作的想法,因为这不是他们的强项。如果那是你——很可能是——我有好消息要告诉你。首先,你不需要写一封杀手级的求职信。其次,这是这个领域深藏不露的秘密,只有极少数人会承认:我们不喜欢读求职信,因为大多数都很糟糕。
所以,如果你写了一封求职信,我们会阅读它——然后把它拆开。你只需要知道:在互联网上的每个求职网站上跟随长格式的“基本”求职信你会让自己暴露在很大的风险中。你可能看起来很无聊,暴露出糟糕的写作技巧,用明显的复制/粘贴求职信模板来烦我们,看起来傲慢,看起来不知道自己在说什么。不过,最重要的是,如果写作对你来说不自然,你就有可能无法展示你真正的优势和个性。
你知道吗,你可以用这种超小的求职信来打发时间?
亲爱的招聘经理女士,
我申请的是高级数据科学家的职位(工作 id: 1234534)。随函附上我的简历和详细的工作经历。根据工作描述,我认为这个职位看起来既有趣又有挑战性——也是我进一步发展职业生涯的好地方!如果您有时间,我想问您更多关于您正在使用的工具以及如果被聘用我可能运行的项目类型的问题。
如果你对我的简历有任何问题,请随时通过电子邮件联系我,或者直接拨打我签名中的电话号码。
希望尽快收到你的来信,
约旦
就是这样!简短,甜蜜,切中要害。最重要的是,它让你看起来正常、有趣、有人情味。对我来说,这是一封完美的求职信。不要胡说什么公司是世界上最好的,或者为什么申请人认为他们比其他人都好。这封信只是表达了我们大多数人不喜欢阅读求职信的对这个角色的兴奋和敬意。看完心情很好。
这些信非常容易写,如果你遵循以下指导方针,你会发现你是最成功的:
健全的人类
如果你总是遵循样板求职信,你听起来会像个机器人。这并不是说招聘经理能看穿这一点,因为他们很聪明,而是每个人都能看穿这一点,因为这很明显。一遍又一遍地阅读相同的模板是令人讨厌和乏味的。帮自己一个忙,不要写别人认为你应该说的话,写自己认为应该用自己的声音说的话。
不要做马屁精
我无法告诉你看那些滴着没有实质内容的赞美的求职信有多讨厌。不要毫无必要地对招聘经理大加赞扬,希望这会给你带来好感,让你成为佼佼者。招聘经理实际上知道在那里工作是什么样的,在一个糟糕的工作日,你的奉承会让你大开眼界。
只要记住:你从未在那里工作过,真的不知道那里是什么样的。但你很想知道更多,对吗?所以提一下。
觉得你很适合吗?再想想,这不是你能决定的。
听着,那些求职信网站是完全错误的。你应该肯定不是包括这样的短语:“我认为我的背景和技能使我非常适合这个职位。”你对该职位的了解是基于人力资源编辑和该工作要求的浓缩版本。招聘经理决定谁是合适的人选,而不是你。
既然我已经挑战了你关于求职信应该是什么样子的观念,我想给那些真的真的讨厌写作的申请人一些建议。
不要写求职信
说真的,这是为大多数申请数据科学和分析职位的人准备的。我们讨厌读求职信(除非它们很棒),你讨厌写求职信。在我们看你的简历之前,一封糟糕的、令人讨厌的或者平庸的求职信会让我们心情不好。
许多申请人提供一封求职信,只是因为他们认为自己需要一封求职信,这严重伤害了他们自己。但是一份有趣的、精心制作的、有扎实工作经验的简历才是我们真正需要的。
如果你申请的是一家使用 Taleo 或类似工具的大公司,在需要求职信的地方,写一份类似上面的简短声明,甚至更短:
致相关人员,
我很高兴申请首席数据科学家的职位。我的简历包含了详细的工作经历和我的成就。
请让我知道你有什么问题,
约旦
保存为 PDF 格式并上传。(我找到了一份做这个的工作。)
结论
求职信的建议包括从遵循上面描述的可靠格式到做一些古怪的事情(比如制作一封可视化的求职信,这是你绝对不应该做的)。请记住,你的竞争对手可能会读到同样的建议,这意味着你的求职信将会看起来像他们的。
我意识到你们中的一些人想要挑战我的建议。有成千上万的博客在讨论如何写一封完美的求职信,但只有我。但我不会给你关于求职信的一般性建议。我说的是我亲身经历的所见所闻。
看,你的求职信,如果你写的话,应该看起来像你。如果你喜欢写作——这是你的一部分——那就给自己写一封长信(无论你做什么,都要避免“基本”格式)。但是如果你在写作中感到不舒服,你会发现保持简短、直接、切中要点会让你更有自我。所以,如果你能像这篇文章中的段落一样写一封简洁的求职信,那就一定要写!但是,如果你不喜欢写作,那就跳过它吧!
除非他们很了不起,否则我们不喜欢看求职信。你不会因为没有包括一个而被评判。
留下评论(和/或掌声)
喜欢我的建议吗?或者,你认为我只是普通的坚果?请在评论中告诉我。还有别忘了留个掌声(或者更多!)如果你喜欢你读过的。
乔丹·戈德梅尔是Cambia Factor Consulting的首席数据治疗师,在那里他帮助公司将数据转化为不仅仅是另一个商业计划,并且是 Excel 的创始人。TV ,一个帮你超越自我的社区。过去的客户包括:亚马逊,原则金融,辛辛那提大学,北约训练任务,和空军研究实验室。 跟我合作。
在简历或求职信方面寻求帮助?让我知道。
用 RNNs“破解”莫尔斯电码
剧透:莫尔斯电码其实不需要破解。它很有用,因为使用这种代码可以用最少的设备发送信息,我说它不需要破解,因为这种代码是众所周知的,点和破折号的组合代表什么不是秘密。但是,理论上,它是一种替代密码——字母表中的每个字母(和每个数字)都用点和破折号来表示,如下图所示。
International Morse code
让我们暂停怀疑,假设我们收到了莫尔斯电码的信息,但我们不知道如何阅读它们。还假设我们有一些代码及其相应单词的例子列表。现在,我们可能会猜测这是一个替代密码,然后最终找出每个字母的代码;从而解码消息。
或者——我们可以构造一个 编码器-解码器模型 来猜测(几乎)所有的单词!!作为受虐狂,我们当然会选择后者。话虽如此,让我们鞭策马,踏上与风车作战的征程。
这里是手头的问题;我们有几个编码序列及其可理解对应物的例子。使用这些例子,我们必须学习一些模式,并使用这些信息来预测新的编码标记(单词)可能是什么。与我们预测数字结果的常见回归问题不同,我们手头有一个序列到序列的学习问题,其中数据中有时间结构。这是递归神经网络(RNNs)可能有所帮助的一个即时提示(格言是 RNNs 用于语音和语言数据,CNN 用于图像数据,以及组合 RNNs 和 CNN 用于图像字幕)。粗略地说,这属于包含机器翻译问题的一类问题;这个模型的结构是这里的灵感来源。关于这个主题的更多信息,请参考[1]。我们在这里不会花时间在 RNNs 的理论上,但是对于这个主题的清晰简明的介绍,请参考[2]中的一系列文章。
对于那些想知道这个问题是否可以用不同的方式解决的人来说;是的,马尔可夫链蒙特卡罗可以得到类似的结果。在这种情况下,我们将遵循优秀论文[3]第一个例子中提到的程序。
大意
粗略地说,我们想从一个输入序列(x1,…x_n)中预测某个输出序列(y1,…,y_m),这涉及到学习条件概率
这里的一个主要障碍是从可变大小的输入预测可变大小的输出。在元级别上,这可以通过组合两个 rnn 来克服,第一个 rnn 将可变大小的输入映射到固定长度的输出,另一个 rnn 接受固定长度的输入并返回可变长度的输出。固定长度的中间向量称为上下文向量,它封装了来自输入序列的信息,每次输入一个字符。产生上下文向量的机制使得 RNNs 可用于捕获时间结构,上下文向量或者是最终时间步长后 RNN 的隐藏状态,或者是它的某个函数。使用链规则计算上述条件概率
其中 h 是上下文向量。最后,可以使用 softmax 函数来计算上述等式右侧的条件概率,该函数将字符 y_{i-1}、…、y_1 的独热编码向量、第二 RNN 中的递归层的输出和上下文向量作为输入。这里使用的特定类型的 RNN 是 LSTM,其有效地克服了简单 rnn 的局限性,简单 rnn 遭受消失梯度问题,并且更好地捕捉长程相关性。
数据准备
我们将引入一个特殊字符()来表示每个字母的代码之间的空格。例如,SOS 的代码将表示为。。。 — — —*.。.‘(而不是’。。。— — — .。.’).我们这样做是为了确保对应于给定代码的单词是唯一的。接下来,我们将使用由编辑的数据集(words_alpha)中的英语单词作为我们数据的单词,而不是产生随机的字母集合。为了理解这些数据,考虑下面给出的单词长度直方图。从直方图可以明显看出,长单词(长度大于 5)比短单词多得多。
根据包含长编码单词的数据训练的网络倾向于平均预测长单词。请记住,网络不会找出产生数据的“公式”,也就是说,它不会学习图 1 中的图表。
我们从构造一个函数开始准备数据,这个函数将把一个英语单词作为输入,并输出它的莫尔斯电码。
import random
import numpy as np
import matplotlib.pyplot as plt *# construct the Morse dictionary* alphabet = " ".join("abcdefghijklmnopqrstuvwxyz").split()values = ['.-', '-...', '-.-.', '-..', '.', '..-.', '--.', '....', '..', '.---', '-.-', '.-..', '--', '-.','---', '.--.', '--.-',
.-.', '...', '-', '..-', '...-', '.--', '-..-', '-.--', '--..']morse_dict = dict(zip(alphabet, values))def morse_encode(word):
return "*".join([dict_morse_encode[i]for i
in " ".join(word).split()]
出于说明的目的,我们将从给定固定长度的单词中产生训练和验证数据。这里我们将这个长度固定为 9,因为长度为 9 的单词的数量足够大(参考上面的直方图)。请注意,这意味着从网络输出的字将是固定长度的,但输入的莫尔斯电码不会都是相同的长度。我们采取的另一个自由是,我们假设我们知道每个字母表由长度最多为 4 的字符串编码(我们不需要做出这个特定的假设,我们可以改为选择训练数据中最长的莫尔斯码的长度作为要跟随的 max_length_x 值)。因此,如果单词 if 的长度为 n ,那么对应于它的莫尔斯电码的长度将最多为 4n+(n-1),其中 n-1 项对应于s 的数量。我们在代码的左侧填充空格以使它们都具有相同的长度,这意味着我们的输入字符词汇表是{ ‘。’、“—”、“”、“},并且为了一般性,我们让输出的字符词汇表是所有字母和空白的特殊字符。回到关于网络平均猜测长词的评论,我们的意思是网络将倾向于猜测更少的空格,因为长词数量造成的不平衡。在下面的代码片段中,output_list 将包含英语单词,input_list 将包含填充的莫尔斯电码。
import randomword_len = 9
max_len_x = 4*word_len + (word_len-1)
max_len_y = len_worddef data_gen(n):
with open('words_alpha.txt', 'r') as f:
all_words = f.read().lower().split('\n')
words = [word for word in all_words if len(word)==n]
*# Shuffle the list since the words are ordered*
random.shuffle(words)
g_out = lambda x: ' '*(max_len_y -len(x)) + x
output_list = [g_out(word) for word in words]
g_in = lambda x: morse_encode(x)+' '*(max_len_x
- len(morse_encode(x)))
input_list = [g_in(word) for word in words]
return output_list, input_listoutput_list, input_list = data_gen(9)
现在,我们构建输入中字符的一个热编码向量,以使输入数据适合神经网络。为此,我们构建了一个类对象(类似于 Keras 文档中的例子),它将有助于把莫尔斯电码和英语单词编码和解码成数组。我们用适当的字符集将类分配给对象。
class CharTable(object): def __init__(self, chars):
self.chars = sorted(set(chars))
self.char_indices = dict((c, i) for i, c in
enumerate(self.chars))
self.indices_char = dict((i, c) for i, c in
enumerate(self.chars)) def encode(self, token, num_rows):
x = np.zeros((num_rows, len(self.chars)))
for i, c in enumerate(token):
x[i, self.char_indices[c]] = 1
return x def decode(self, x, calc_argmax=True):
if calc_argmax:
x = x.argmax(axis=-1)
return ''.join(self.indices_char[x] for x in x)*# we include the white space as a character in both cases below.*
chars_in = '*-. '
chars_out = 'abcdefghijklmnopqrstuvwxyz 'ctable_in = CharTable(chars_in)
ctable_out = CharTable(chars_out)
分割数据以从整个数据集 x,y 的四分之一中产生训练集 x_train,y_train,并且我们将保留剩余的四分之三作为验证集 x_val,y_val。请注意,理想情况下,我们应该将训练集的一部分作为验证集,其余部分作为测试集,但是考虑到我们的玩具设置,我们对模型构建比对参数调整更感兴趣。我们现在已经准备好了我们的训练和测试(验证)数据,可以继续修补网络了。
x = np.zeros((len(input_list), max_len_x, len(chars_in)))
y = np.zeros((len(output_list), max_len_y, len(chars_out)))for i, token in enumerate(input_list):
x[i] = ctable_in.encode(token, max_len_x)
for i, token in enumerate(output_list):
y[i] = ctable_out.encode(token, max_len_y) m = len(x)// 4
(x_train, x_val) = x[:m], x[m:]
(y_train, y_val) = y[:m], y[m:]
构建神经网络最简单的方法是使用 Keras 模型和顺序 API。因为我们不需要 TensorFlow 的全部功能和灵活性,所以让我们坚持使用 Keras。
模型构造(编码器-解码器模型)
我们选择的模型拓扑将包含简单 RNN 的一个强大变体,称为长短期记忆(LSTM)网络。
第一个 LSTM 将作为编码器,接收可变长度的输入序列,一次一个字符,并将其转换为固定长度的内部潜在表示。另一个 LSTM 将作为解码器,将潜在表示作为输入,并将其输出传递到密集层,该层使用 softmax 函数一次一个字符地进行预测。
该模型的编码器和解码器组件可能具有多层 LSTMs,并且通常事先不清楚哪种拓扑工作得最好。对于机器翻译来说,深度网络通常工作得更好。根据经验,我们希望堆叠图层能够学习更高级别的时态表示,因此当数据具有某种层次结构时,我们会使用它。对我们来说,一层就够了。
该模型是使用 Sequential()构建的,每次添加一层。第一 LSTM 层将 3D 张量作为输入,并要求用户指定输入尺寸。这可以用代码中指定的 input_shape 简洁地完成,其中第一个组件表示时间步长的数量,第二个组件表示特征的数量。对我们来说,特征的数量是输入序列的词汇表中元素的数量,即 4,因为我们有“.”、“—”、“*”和空白字符“”。时间步长的数量是 max_len_x,因为我们一次馈送一个独热编码向量。我们还将指定层中存储单元(或块)的数量(这里由 latent_dim 参数表示,我们使用 256),这是潜在表示的维度。注意,我们想要返回 LSTM 的最终隐藏状态作为潜在表示,这将具有来自所有时间步骤的信息,即完整的输入序列。如果我们使用 return_sequences = true 选项,我们将获得每个时间步长的隐藏状态输出,但它将只包含到该步为止的序列信息。
model = Sequential()
model.add(layers.LSTM(latent_dim, input_shape=(max_x_length,
len(chars_in))))
这就结束了简单的编码器模型。接下来我们构建一个类似的层作为我们的解码器。但是,上述代码片段的输出将是一个 2D 数组。我们通过使用方便的 RepeatVector 层重复输出 max_len_y 次,将其转换为 3D 张量,并将其用作下一个 LSTM 层(解码器)的输入。现在,我们使用这个 LSTM 中的 return_sequences=True 选项来输出隐藏状态的序列,并且我们需要使用这个信息来进行预测。为此,我们使用时间分布密集层,它输出长度为 max_len_y 的向量,在该向量上,我们使用 softmax 激活函数来挑选最可能的字母。要快速了解时间分布层的用途,请参考这篇博客文章。
model.add(layers.LSTM(latent_dim, return_sequences=True))
model.add(layers.TimeDistributed(layers.Dense(len(chars_out))))
model.add(layers.Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
model.summary()
这里是网络和各种输入和输出的尺寸的快速总结。
我们将模型与数据进行拟合,在 x_train,y_train 集合上进行训练,并使用 x_val 和 y_val 来查看我们做得有多好。我们需要设置的最后一组参数是时期数和批量大小。批量大小是在梯度下降算法中通过网络的训练集部分的大小,在此之后对网络中的权重进行更新。通常批量大小被设置为你的计算机内存所能处理的最大值。一个历元是使用这些批次的训练数据的完整运行。这里,我们将批量大小设置为 1024,使用 120 个历元,从下图中可以看出,经过大约 100 个历元后,精度没有明显提高。一般来说,这是一个试验和错误的问题,看看哪些参数的工作。我们现在使用 fit()方法来拟合模型。
Epochs = 120
Batch_size = 1024hist = model.fit(x_train, y_train, batch_size=Batch_size, epochs=
Epochs, validation_data=(x_val, y_val))plt.figure(figsize=(20,5))
plt.subplot(121)
plt.plot(hist.history['acc'])
plt.plot(hist.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper left')plt.subplot(122)
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'validation'], loc='upper right')
plt.show()
最后,从上面的图表可以看出,我们可以在验证集上获得大约 93%的准确率,这还不错。当然,如果我们增加训练数据的规模,我们可以做得更好。以下是对随机选择的一组单词的一些预测。
Input codes on the left, the corresponding words in the middle and the predictions on the right. The word is green if predicted correctly and red if not.
如你所见,错误的预测也不算太糟糕。我们必须提醒自己,破解密码,也就是说,找出每个字母代表什么,并不能破解密码。事实上,我们可以输入字母表的代码,看看网络对单个字母的代码预测是什么,正如你在下面看到的,我们错了!
作为编码器-解码器模型的另一个例子,你可以尝试使用凯撒密码或另一种代码,看看这种方法有多有效。
参考文献:
[1]http://papers . nips . cc/paper/5346-用神经网络进行序列对序列学习. pdf
[2]http://colah.github.io/posts/2015-08-Understanding-LSTMs/
[3]http://www . AMS . org/journals/bull/2009-46-02/s 0273-0979-08-01238-X/s 0273-0979-08-01238-X . pdf
打造数据体验
以用户为中心的框架已经成为许多不同讨论的关键,包括但不限于软件设计、新兴商业模式、新的工作方式、新的白领工作,甚至我们如何组织我们的社会。尽管这种不断增长的以用户为中心的狂热的优点和需求不可否认是重要的,但它似乎确实把一切都放在了同一个盒子里。有时,我们必须打破已知,才能想象出真正独特的东西……尤其是在数据体验方面。
所以,问题来了:总有那么多事要做!总有一千种不同的事情可以轻松解决,具有巨大的价值并加速上市…因此,数据解决方案通常要么是组装仪表板的传统方法,要么是一种艰巨的努力…通过严格关注显而易见的需求/用户,我们总是努力攀登最高的山峰,而不是挑战极限以征服月球。
现在,当你在讨论中引入一些被错误地认为是“严格技术性”的东西,比如数据解决方案时,以用户为中心的框架通常会逐渐消失,变成标准的比萨饼、条形图和折线图,用花哨的颜色或图案显示用户习惯的确切信息。突发新闻:这既不应该被认为是以用户为中心的,也不是数据解决方案。
数据体验
在定义什么是数据体验之前,明确什么不是很重要。自动化不是数据体验!!虽然有人可能会说,收集或创建、处理和分析数据的纯粹事实是自动化的核心,但这里我们不包括使用复杂数据分析来取代人类的解决方案,从你的冰箱在牛奶用完时向市场下订单到无接触工业供应链管理。
在这种情况下,数据体验是那些利用数据的力量为用户提供对给定上下文的可操作理解的体验。通常被称为智能放大或增强智能,它们利用技术作为扩展一个人收集和处理信息的能力的手段,但最终它取决于一个人做出决定的责任(并最终改造系统以提高其准确性)。这意味着,为了恰当地制作增强智能解决方案,用户必须是该过程的关键部分,因为他的专业知识和经验是产品中利用的关键组件。否则就像设计一个手机壳,却不知道手机的大小和形状,也不知道摄像头、传感器、插头和按钮的位置。
通过利用以用户为中心的思维模式的核心,并接受共享人机知识体的可能性,就有可能设计出独一无二的数据体验。
从一个明确的目标开始
所以这就是棘手的地方,如果你的目标太宽泛,你的解决方案要么太复杂,要么不可行。另一方面,如果你设定了一个小目标,你要么限制了你的可能性,要么可能忽略了问题的真正根源。有鉴于此,一个明确的目标不是你的最终使命“世界和平”,而是你可以实现的目标“优化 X 地区的货物分配”。
创造环境意识
当谈到数据解决方案时,有些技术方面和解决方案的人的方面一样重要。请记住这一点:数据体验利用数据来增强采取行动的能力。有鉴于此,为了确保正确理解上下文,你应该能够回答几个问题:
- 谁是我的用户,他们的需求是什么?
- 他们目前是如何实现目标的?
- 影响他的不同方面有哪些,影响到什么程度?
- 你的用户对这种信息有多熟悉,他有多信任它?
- 我的用户需要多少信息/细节才能有信心采取行动?
- 哪些数据是可用的或可以创建的?
我也喜欢在这个列表中加上“技术上可能的”。但是需要强调的是,这不应该是对技术方面的深入调查。对不同类型的数据解决方案(包括描述性、诊断性、规定性、预测性和人工智能,有监督和无监督的)的关键方面以及总体数据策略方面的总体理解应该会让您有所了解。
在可能性上有分歧
在这种解决方案中,构思更多的是提出假设,而不是其他任何事情。如果你的目标是让你的电影获得最多的观众,这一刻应该是关于“天气影响人们对喜剧的开放程度”,而不是关于“在公交车站创造活动”。这是反映可能性或添加尚未关联的新信息的时刻。我记得有一次我们对 IT 部门提供的一些服务进行了调查。我们从单独的调查开始,然后决定一起分析他们的结果。我们发现的最有趣的事情之一是,wifi 的质量会影响员工对其电脑的满意度。现在,停下来想一想,现在大多数应用程序都在云上运行,而且您的大多数文件都存储在云驱动程序中……如果 wifi 速度很慢,这将影响应用程序的加载时间,或者您访问文件的时间,这可能会导致错误的看法,认为问题出在计算机本身。有鉴于此,wifi 升级可能是比电脑更新更好的投资。探索数据的可能性是关于探索不可预见的关系和看待假设已知事物的新方法。
就一个解决方案达成一致
选择你的开始假设,并开始测试它!!数据解决方案是关于集合重要的事物和加速采取行动的能力。这意味着,当你开始处理真实数据时,你很可能会推翻许多假设,或者你可能会放弃显而易见的陈述,为相关的发现留出空间。
请记住:你对自己的分析结果越有把握,就越没有必要,也越有可能忽略了一些重要的东西。很像这样:你应该专注于理解一个地方的地理位置和太阳升起的时间之间的关系,而不是太阳是否每天都会升起。
另外,带有可视化的原型制作极其重要!这既是在设想要测试哪些时,也是在用真实数据进行测试时。当对数据进行原型设计时,我喜欢开始理解它的整体属性(它的格式、它的含义以及它变化的频率),并在选择一两个用数据和用户进行测试之前画一些图表。
调整,增强,重复!
唯一不变的是一切都在变…这意味着你永远不会放弃数据体验!你应该不断测试看待同一事物的新方法,最重要的是,尝试新的事物来聚合和丰富你的工作。
最后但同样重要的是:数据体验不仅仅由可视化组成,它通常还需要开发额外的功能和动作。有鉴于此,确保在您的假设被测试之前,不要花费太多精力在与分析结果密切相关的特性上,否则您可能会在一些要丢弃的东西上浪费时间。
通过 Flask、ElasticSearch、javascript、D3js、异步请求(xml http 请求)和 Bootstrap 创建一个完整的搜索引擎
一个搜索引擎是一个系统——显示一些过滤器——为了定制你的搜索结果,让你准确找到你想要的。当用户查询搜索引擎时,基于搜索引擎的算法返回相关结果。
过滤器变成一个列表(下拉列表)、一个表格……任何允许扩大或缩小搜索的元素,以便获得相关结果(你实际感兴趣的结果)。
谷歌是“斯巴达搜索”的王者,也是世界上使用最多的搜索引擎。谷歌提供了一个完全免费的文本搜索过滤器。用户将关键词或关键短语输入到搜索引擎中,并接收个人的和相关的结果。在这篇文章中,我们将通过谷歌。
当谈到葡萄酒评论时,让我们考虑一个来自葡萄酒爱好者的数据集。刮刀的代码可在处找到。搜索引擎仍然无法品尝葡萄酒,但理论上,它可以根据侍酒师的描述检索出葡萄酒。
- 国家 :葡萄酒来自的国家
- 描述 :品酒师描述葡萄酒的味道、气味、外观、感觉等的几句话。
- 名称 :酿酒厂内酿造葡萄酒的葡萄所来自的葡萄园
- :葡萄酒爱好者给葡萄酒打分的分数,范围从 1 到 100(尽管他们说他们只给得分> =80 的葡萄酒发评论)
- 价格 :一瓶酒的成本
- 省 :葡萄酒所来自的省或州
- 品种 :用来酿酒的葡萄种类(即黑皮诺)
- 酒厂 :酿造葡萄酒的酒厂
为了揭开旅程的序幕,我们将重点介绍 三个特定国家 和 三个地区 的葡萄酒。
第一步:如何过滤来自国家和地区的葡萄酒数据集?
如果您知道 【波尔多】 或 【香槟】 是 法国 内的地区,知道 伦巴第 和 托斯卡纳 是 意大利 的地区,最后知道 安达卢西亚 和的地区,这种方法就很有效如果你不这样做,你就被困住了,什么也得不到。这是一个巨大的问题,因为你必须尝试所有的可能性,才能幸运地得到一些东西。如何处理这个问题?通过在过滤器中实现父/子逻辑。
说到 app 本身——有后端(app.py)和前端(index.html)。当您在下拉字段中选择一个元素时,一个 XMLHttpRequest 被发送到服务器,并返回结果(从服务器)。XMLHttpRequest 对象可用于从 web 服务器请求数据。
App.py
XMLHttpRequest 对象是开发人员的梦想,因为您可以:
- 更新网页而不重新加载页面
- 从服务器请求数据—在页面加载后
- 从服务器接收数据—在页面加载后
- 在后台向服务器发送数据
onreadystatechange 属性指定每当 XMLHttpRequest 对象的状态改变时要执行的函数:
模板/索引. html
第二步:我们如何在 JS 中通过父属性过滤子属性?
当一个给定的下拉列表依赖于另一个下拉列表时,拥有所有可能的选项并不令人愉快。如果你点击法国,然后是加泰罗尼亚,我们就被困住了,什么也得不到,因为加泰罗尼亚不在法国…太糟糕了。要么你尝试所有的可能性来得到某些东西,要么我们实现如下的父子属性:
父子属性:
第三步:将数据加载到 ElasticSearch 中
将 ElasticSearch 数据库连接到 python。 Elasticsearch 是基于 Lucene 库的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP web 接口和无模式的 JSON 文档。Elasticsearch 已经迅速成为最受欢迎的搜索引擎,通常用于日志分析、全文搜索、安全智能、业务分析和运营智能用例。
*from time import time
from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk
import pandas as pdimport requests
res = requests.get(‘[http://localhost:9200'](http://localhost:9200'))
print(res.content)#connect to our cluster
from elasticsearch import Elasticsearch
es = Elasticsearch([{‘host’: ‘localhost’, ‘port’: 9200}])*
将一个 csv 文件转换成 json 格式放入 ElasticSearch。
***def** index_data(data_path, index_name, doc_type):
**import** json
f = **open**(data_path)
csvfile = pd.read_csv(f, iterator=True, encoding=”utf8")
r = requests.get(‘[http://localhost:9200'](http://localhost:9200'))
**for** i,df **in** enumerate(csvfile):
records=df.where(pd.notnull(df), None).T.to_dict()
list_records=[records[it] for it in records]
**try** :
**for** j, i **in** enumerate(list_records):
es.index(index=index_name, doc_type=doc_type, id=j, body=i)
**except** :
print(‘error to index data’)*
额外收获:烧瓶词汇
1.路由技术
用于直接访问所需页面,而无需从主页导航。 route() 装饰器用于将 URL 绑定到函数。
*@**app**.**route**(‘/hello’)
**def** hello_world():
**return** ‘hello world’*
URL*‘/hello’*规则绑定到了 hello_world() 功能。因此,如果用户访问http://localhost:5000/helloURL,则 hello_world() 功能的输出将在浏览器中呈现。
应用程序对象的 add_url_rule() 功能也可用于绑定 url,功能如下:
***def** hello_world():
**return** ‘hello world’
**app**.**add_url_rule**(‘/’, ‘hello’, hello_world)*
2.HTTP 方法
Http 协议是万维网数据通信的基础。
模板/索引. html
*<html>
<body>
<form action = "http://localhost:5000/login" method = "post">
<p>Enter Name:</p>
<p><input type = "text" name = **"nm"** /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
</body>
</html>*
app.py
*from flask import Flask, redirect, url_for, request
app = Flask(__name__)
@**app.route**('/success/<name>')
def success(name):
return 'welcome %s' % name
@**app.route**('/login',methods = ['POST', 'GET'])
def login():
if **request.method** == '***POST***':
user = request.form['**nm**']
return redirect(url_for('success',name = user))
else:
user = **request.args.get**('**nm**')
return redirect(url_for('success',name = user))
if __name__ == '__main__':
app.run(debug = True)*
3.模板
可以以 HTML 的形式返回绑定到某个 URL 的函数的输出。
hello() 功能将渲染*【Hello World】*并附上 < h1 > 标签。
*from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '**<html><body><h1>**'Hello World'**</h1></body></html>**'
if __name__ == '__main__':
app.run(debug = True)*
从 Python 代码生成 HTML 内容非常麻烦,尤其是当需要放入变量数据和 Python 语言元素(如条件句或循环)时。这将需要频繁逃离 HTML。
这就是人们可以利用 Flask 所基于的 Jinja2 模板引擎的地方。可以通过 render_template() 函数来呈现 HTML 文件,而不是从函数中返回硬编码的 HTML。
*from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello/<user>')
def hello_name(user):
return render_template('hello.html', name = user)
if __name__ == '__main__':
app.run(debug = True)*
***【Web 模板系统】*指设计一个动态插入变量数据的 HTML 脚本。网络模板系统由模板引擎、某种数据源和模板处理器组成。
砂箱采用京 a2 模板发动机。web 模板包含 HTML 语法,其中散布了变量和表达式(在本例中为 Python 表达式)的占位符,当呈现模板时,这些占位符将替换为值。
以下代码保存为模板文件夹中的hello.html。
*<!doctype html>
<html>
<body>
<h1>Hello **{{ name }}**!</h1>
</body>
</html>*
jinga模板引擎使用以下分隔符来转义 HTML。
- { %……% }用于语句
- {{ … }}将表达式打印到模板输出
- { #……# }对于模板输出中未包含的注释
- #……# #用于行语句
hello() 函数的 URL 规则接受整数参数。传递给hello.html*模板。在其中,比较接收到的数字(标记)的值(大于或小于 50),并据此有条件地呈现 HTML。*
*from flask import Flask, render_template
app = Flask(__name__)[@app](http://twitter.com/app).route('/result')
def result():
dict = {'phy':50,'che':60,'maths':70}
return render_template('result.html', result = dict)if __name__ == '__main__':
app.run(debug = True)<!doctype html>
<html>
<body>
<table border = 1>
{% for key, value in result.iteritems() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
</body>
</html>*
示例:Jquery、AJAX 和烧瓶
[在建…待完成]
通过 3 个简单的步骤,从你的谷歌位置历史创建一个热图
毫不奇怪,像谷歌这样的公司几乎在跟踪我们的一举一动——如果我们允许的话。然而,通常情况下,访问我们自己正在收集的数据,并以有趣的方式利用它似乎比它应该的更难。在这篇文章中,我将向你展示一个快速的方法,帮助你标出你曾经在世界上的什么地方。好吧,反正既然你开始用安卓手机了。
我一直对我们记忆的工作方式很感兴趣。有许多线索可以帮助我们从大脑深处找回记忆——气味、照片、声音和其他感觉。不同的暗示对不同的人起作用(我的女朋友因为我不记得某些美味晚餐的味道而生我的气。).我发现位置对我来说非常有用,这使得在地图上标出我的每一步行动的能力特别有吸引力。**一旦我知道我去过哪里,我就能回忆起大部分时间我在那里做了什么。**有许多方法可以做到这一点——草稿图是一种流行的方法。如果你想获得更高的保真度,你可能最终会进入数字世界。以下是方法:
- 首先,让你的头脑被你的谷歌时间表所震撼。一定要在笔记本电脑上检查一下,因为移动版本并没有给你所有的功能。这张整洁的地图显示了你去过的所有地方。即实际场所如酒吧、餐厅、公司等。这是一个好的开始,但我希望能够看到我去过的每一个地点,我在那里呆了多长时间或多长时间,以及我是如何在它们之间旅行的。因此,让我们利用我们的数据创建一个热图来实现这一点。
My Google Timeline — Yeah, I remember that drive down through Italy
2)到谷歌外卖,到下载你的位置数据。取消选择所有内容,然后检查“位置历史 JSON 格式”。在下一步中,将您的数据下载为 zip 存档,然后解压缩。现在,您的计算机上应该有一个 JSON 文件,其中包含您的手机存储您的位置时的每个实例(如果您启用了位置服务,这种情况会比您想象的更频繁)。请注意,所有这些可能需要一段时间,这取决于有多少关于你的数据。在我过去的三年里,这大约是 200mb。
Download your Location History from Google Takeout
3)从位置历史可视化工具转到免费使用的热图功能。他们也有一个付费产品,功能增强,你可以看看(注:我与这家公司没有任何关系。我通过谷歌搜索找到了他们,觉得他们的产品很酷。).类似的服务还有很多,但大多数都有非常低的文件大小限制。
上传你的 JSON 文件——et voilà,ready 就是你整个位置历史的热图。你可以像谷歌地图一样导航,还可以随意截图。
The heat map adjusts itself to the zoom factor. So many trips to Paris…
如您所见,这是一件相当简单的事情。你可以用它来回忆你徒步旅行的路线,你穿越欧洲的公路旅行,或者你在巴黎的观光旅行;或者向你的朋友炫耀你去过的所有地方。玩得开心!
@ Thomas _ Essl是写东西的设计师。他还是《七件事》时事通讯和《产品掘金》 播客的制作人。在www.thomasessl.com找到更多他的作品。
使用 Python 创建预测房价的模型
你好,
上次我们看到了如何在泰坦尼克号数据集上进行逻辑回归,许多专业数据科学家认为这是进行数据科学项目的第一步。如果你没有,你可以在这里找到它,
你好,
medium.com](https://medium.com/towards-data-science/how-to-begin-your-own-data-science-journey-2223caad8cee)
所以,我假设你知道 python 的基本库(如果不知道,那么浏览上面的教程)。我们将使用上次使用的相同库,并添加了 seaborn,这是另一个内置的 python 库,用于进行数据表示。
上一次,我们做了一个数据集,其中有泰坦尼克号乘客的数据,我们知道泰坦尼克号发生了什么,我们不需要浏览数据集。但是大多数时候,数据科学家得到的是他们不知道的数据。深入了解数据非常重要。
到目前为止还不错,今天我们要处理一个数据集,它包含了房子的位置、价格和其他方面的信息,如平方英尺等。当我们处理这类数据时,我们需要了解哪一列对我们来说是重要的,哪一列是不重要的。我们今天的主要目标是建立一个模型,它能根据其他变量给我们一个好的房价预测。我们将对这个数据集使用线性回归,看看它是否能给我们一个好的准确度。
什么是好的准确性?嗯,这取决于我们正在处理的数据类型,对于信用风险数据来说,80%的准确率可能不够好,但对于使用 NLP 的数据来说,这就很好了。因此,我们实际上不能定义“良好的准确性”,但任何高于 85%的都是好的。我们在这个数据集上的目标是达到 85%以上的准确率
让我们开始吧,数据和代码可以在我的 github 链接上找到
[## Shreyas3108 (Shreyas raghavan)
Shreyas3108 有 9 个可用的存储库。在 GitHub 上关注他们的代码。
github.com](https://github.com/shreyas3108)
首先,我们导入我们的库和数据集,然后我们看到数据的头部以了解数据的外观,并使用 describe 函数来查看百分点和其他关键统计数据。
Starting , by importing libraries and reading dataset
Knowing more about the dataset
从上面描述的函数中我们可以推断出什么?
- 看看卧室列,数据集有一所房子,房子有 33 间卧室,看起来是一所大房子,随着我们的进展,了解更多会很有趣。
- 最大平方英尺是 13,450,最小平方英尺是 290。我们可以看到数据是分布的。
同样,我们只看 describe 函数就能推断出这么多东西。
现在,我们将看到一些可视化,也将看到我们如何从可视化中推断出什么。
哪栋房子最常见(卧室方面)?
让我们看看哪个是最常见的卧室号码。你可能想知道为什么它很重要?让我们从建筑商的角度来看这个问题,有时对于建筑商来说,重要的是要看哪种房子卖得最好,这使得建筑商能够以此为基础建造房子。在印度,一个好地段的建筑商选择建造超过 3 间卧室的房子,这吸引了更高的中产阶级和社会的上层阶级。
让我们看看这些数据的结果如何?
House bedrooms and count
正如我们从可视化中看到的,三居室的房子最常出售,其次是四居室。那么怎么有用呢?对于一个拥有这些数据的建筑商来说,他可以建造更多的三居室和四居室来吸引更多的买家。
所以现在我们知道三居室和四居室的销量最高。但是在哪个地方呢?
基于纬度和经度可视化房屋的位置。
所以根据数据集,我们在数据集上有每栋房子的纬度和经度。我们要去看看共同位置,看看房子是怎么摆放的。
How houses are placed ?
我们使用 seaborn,我们得到他的美丽的可视化。Joinplot 函数有助于我们看到数据的集中和数据的放置,非常有用。让我们看看我们能从这个可视化中推断出什么。对于纬度在-47.7 和-48.8 之间有许多房子,这意味着它可能是一个理想的位置,不是吗?但是当我们谈到经度时,我们可以看到在-122.2 到-122.4 之间的浓度很高。也就是说大多数买家都去过这个地方。
影响房价的常见因素有哪些?
我们看到了共同的位置,现在我们将看到一些影响房价的共同因素,如果是这样的话?那是多少?
让我们从价格是否受到房屋居住面积的影响开始。
Price vs Square feet and Price vs Longitude
我们上面使用的图被称为散点图,散点图帮助我们看到我们的数据点是如何分散的,通常用于两个变量。从第一张图中我们可以看到,居住面积越大,价格越高。虽然数据都集中在某个特定的价格区域,但从图中我们可以看到,数据点似乎是在直线方向上。由于散点图,我们也可以看到一些不规则性,最高平方英尺的房子卖得很低,也许有另一个因素或可能数据一定是错误的。第二个数字告诉我们房子在经度上的位置,这给了我们一个非常有趣的观察结果,即-122.2 到-122.4 之间的房子售价更高。
Similarly we compare other factors
我们可以看到更多影响价格的因素
Total sqft including basement vs price and waterfront vs price
Floors vs Price and condition vs Price
Which location by zipcode is pricey ?
从上面的描述中我们可以看出,许多因素都在影响着房子的价格,比如平方英尺增加了房子的价格,甚至位置也影响着房子的价格。
现在,我们已经熟悉了所有这些表示,并可以讲述我们自己的故事,让我们移动并创建一个模型,根据其他因素(如平方英尺、滨水区等)来预测房子的价格。我们将会看到什么是线性回归,我们是如何做的?
线性回归:-
简而言之,统计学中的一个模型,它帮助我们根据变量的过去关系来预测未来。所以当你看到你的散点图有线性排列的数据点时,你知道回归可以帮助你!
回归的工作原理是直线方程,y=mx+c,趋势线是通过数据点设定来预测结果的。
Fitting line on the basis of scatter
我们预测的变量称为标准变量,称为 y。我们预测所基于的变量称为预测变量,称为 x。当只有一个预测变量时,预测方法称为简单回归。且如果存在多个预测变量,则进行多重回归。
让我们看看代码,
Linear regression on the data to predict prices
我们使用训练数据和测试数据,训练数据来训练我们的机器,测试数据来看看它是否已经很好地学习了数据。在任何事情之前,我希望每个人都记住机器是学生,培训数据是教学大纲,测试数据是考试。我们看到多少机器已经得分,如果它得分好,模型是成功的。
那我们做了什么?我们一步一步来。
- 我们导入依赖项,对于线性回归,我们使用 sklearn(内置于 python 库中)并从中导入线性回归。
- 然后,我们将线性回归初始化为变量 reg。
- 现在我们知道价格是可以预测的,因此我们将标签(输出)设置为价格列,我们还将日期转换为 1 和 0,这样就不会对我们的数据产生太大影响。对于 2014 年后新建的房屋,我们使用 0。
- 我们再次导入另一个依赖项,将数据分成训练和测试。
- 我把我的训练数据作为 90%,10%的数据作为我的测试数据,用 random_state 把数据的拆分随机化。
- 现在,我们有了训练数据、测试数据和标签,让我们将训练和测试数据拟合到线性回归模型中。
- 将我们的数据拟合到模型后,我们可以检查数据的得分,即预测。在这种情况下,预测值为 73%
这个模型的精确度低于我们 85 的目标。那么,我们如何实现 85%的目标呢?
我们使用不同的方法,这对于像这样的弱预测模型是非常重要的。
这可能看起来有点高级,但是如果理解的话,这是一个非常好的工具,可以实现更好的预测。
为了建立预测模型,许多专家使用**梯度推进回归,**那么什么是梯度推进呢?这是一种用于回归和分类问题的机器学习技术,它以弱预测模型(通常是决策树)的集合的形式产生预测模型。
现在为了简单起见,请记住我们在学生时代是如何映射机器的,训练数据是教学大纲,测试数据是考试数据。让我们试着理解使用相同的梯度推进方法。那么,我们来分析一下为什么我们的学生(机器)没有达到 85%以上?可能有很多原因,但很少有这样的原因
- 我们的学生在考试前忘记了一些题目,同样,机器读取的数据也会丢失。
- 可能是一个不通过阅读学习,但需要视觉化的弱学习者。我们的机器可能是一个弱学习者,可能需要决策树。
- 即使使用了更新的技术,我们的学生也可能记不住教学大纲,所以我们给学生时间去阅读和理解。机器也是如此。
因此,对于所有这些问题,有一个解决办法,梯度下降推进。
梯度推进是构建预测模型的最强大的技术之一。在这篇文章中,你会发现…
machinelearningmastery.com](http://machinelearningmastery.com/gentle-introduction-gradient-boosting-algorithm-machine-learning/)
此链接提供了对梯度推进算法的深入理解。
Gradient boosting graph for understanding
让我们看看我们是如何做到这一点的,然后我们可以深入了解正在发生的事情。
- 我们首先从 sklearn 导入库(相信我,它是所有统计相关模型的最佳库)
- 我们创建一个变量,在这里定义梯度推进回归变量并设置参数
n_estimator —要执行的升压阶段的数量。我们不应该把它定得太高,那会使我们的模型过拟合。
max _ depth 树节点的深度。
learning _ rate 学习数据的速率。
损失—要优化的损失函数。“ls”是指最小二乘回归
最小样本分割—学习数据时要分割的样本数量
3.然后,我们将训练数据拟合到梯度推进模型中,并检查准确性
4.我们得到了 91.94% 的准确率,这是惊人的!
我们可以看到,对于弱预测,梯度提升对相同的训练和测试数据起作用。
点击此链接了解更多关于梯度推进回归器的信息
[## 3.2.4.3.6.sk learn . ensemble . gradientboostingregressor-sci kit-learn 0 . 18 . 1 文档
class sk learn . ensemble . GradientBoostingRegressor(loss = ’ ls ',learning_rate=0.1,n_estimators=100,子样本=1.0…
scikit-learn.org](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html)
我们得到了我们想要的!准确率高达 91.94%。将此技术应用于各种其他数据集并发布您的结果。尝试放置随机种子,检查它是否改变了数据的准确性!如果是的话,请告诉我。谢谢你阅读它!
希望你喜欢它!再见,以后会有更多的模型和内容!请分享推荐给你的朋友。
**更新:**这个的代码可以在 https://github.com/Shreyas3108/house-price-prediction上找到