《计算之魂》阅读笔记 02

  • 1.3 怎样寻找最好的算法
    • 例题 1.3
      • 方法一:三重循环
      • 方法二:二重循环
      • 方法三:分而治之
      • 方法四:正反扫描
    • 【思考题 1.3.1】
    • 【思考题 1.3.2】
    • 【思考题 1.3.3】
  • 总结
  • 参考资料

1.3 怎样寻找最好的算法

例题 1.3

  • 给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间

    • 如序列:1.5, −12.3, 3.2, −5.5, 23.2, 3.2, −1.4, −12.2, 34.2, 5.4, −7.8, 1.1, −4.9
    • 总和最大的区间:从第5个数 23.2 到第10个数 5.4
    • 另一种表述:寻找一只股票的有效增长期

方法一:三重循环

  • 假设

    • 这个序列有 K 个数: a 1 , a 2 , . . . , a K \bm{a_{1}, a_{2}, ..., a_{K}} a1​,a2​,...,aK​
    • 选取区间为: [ p , q ] \bm{[p, q]} [p,q]
    • 区间内的数字总和: S ( p , q ) = a p + a p + 1 + . . . + a q \bm{S(p,q)} = a_{p} + a_{p+1} + ... + a_{q} S(p,q)=ap​+ap+1​+...+aq​
  • 其中
    1. p ∈ [ 1 , K ] \bm{p} ∈ [1, K] p∈[1,K]
    2. q ∈ [ p , K ] \bm{q} ∈ [p, K] q∈[p,K]
    3. 每一对 [ p , q ] \bm{[p, q]} [p,q] 组合平均要做 K / 3 \pmb{K / 3} K/3K/3 次加法
    • 综上,时间复杂度是 O ( n 3 ) \pmb{O(n^3)} O(n3)O(n3)

注意:这里第3点,每对组合平均要做的加法次数,原文给的是 K / 4 K / 4 K/4(有误),勘误如下:

证明:参考大牛思路,可用数学归纳法,如下:


方法二:二重循环

  • 记录三个中间值

    1. p 到当前位置 q 的总和 S ( p , q ) \bm{S(p,q)} S(p,q)
    2. p 到当前位置 q 所有总和中的最大值 Max
    3. 区间结束的位置 r
  • 示例
    • 假设区间起点 p = 500,这时 S(500,500) = a500Max = a500r = 500
    • 遇到 a501 后,根据 a501 是否大于0 决定 是否更新 qMaxr
    • 以此类推,继续往后扫描
  • 分析
    1. p ∈ [ 1 , K ] p ∈ [1, K] p∈[1,K],有 K 种取法
    2. ∀ p ∈ [ 1 , K ] \bm{\forall} p ∈ [1, K] ∀p∈[1,K],需要从头到尾试 K-p
    • 综上,时间复杂度是 O ( n 2 ) \pmb{O(n^2)} O(n2)O(n2)

方法三:分而治之

  • 方法

    • 将序列一分为二: [ 1 , K / 2 ] [1,K/2] [1,K/2], [ K / 2 , K ] [K/2, K] [K/2,K]
    • 分别求两个子序列的总和最大区间 AB
    • 递归算法 求每个子序列的总和最大区间
  • 结论
    • 如果 A、B 间没有间隔,且区间总和均为正整数,则整个序列的总和最大区间就是 [ p , q ] \bm{[p, q]} [p,q]

    • 如果 A、B 间有间隔,假设 A = [ p 1 , q 1 ] A=[p_{1}, q_{1}] A=[p1​,q1​], B = [ p 2 , q 2 ] B=[p_{2}, q_{2}] B=[p2​,q2​],则整个序列的总和最大区间是 [ p 1 , q 1 ] \bm{[p_{1}, q_{1}]} [p1​,q1​]、 [ p 2 , q 2 ] \bm{[p_{2}, q_{2}]} [p2​,q2​] 和 [ p 1 , q 2 ] \bm{[p_{1}, q_{2}]} [p1​,q2​] 中最大的一个

      证明:分情况讨论即可:

    • 时间复杂度是 O ( n l o g n ) \bm{O(nlogn)} O(nlogn)

方法四:正反扫描

  1. 在序列中扫描找到第一个大于0的数,复杂度 O ( n ) O(n) O(n)

    如果所有数字非正(≤0),那么要找的区间就是 整个序列中最大的数

  2. 借鉴方法二,令 p = 1 p=1 p=1, q = 2 , 3 , . . . , K q=2,3,...,K q=2,3,...,K,计算 S ( 1 , q ) \bm{S(1,q)} S(1,q)、Maxf(前向最大)和 r
  3. 扫描到最后( q = K q=K q=K),所保留的 Maxf 对应的 r 就是要找区间的右边界

    如果 ∀ q ∈ [ 2 , K ] \bm{\forall} q ∈ [2, K] ∀q∈[2,K],都有 S ( 1 , q ) ≥ 0 S(1,q)≥0 S(1,q)≥0,情况会比较简单

  4. 反向扫描,同理计算出 Maxb(后向最大)和 l \bm{l} l(左边界)
  • 我们使用例题1.3的数据,依次计算前向累积后向累积值,结果如下:

    从图表中我们易知:

    • Maxf = 39.3,对应 r = 10 r=10 r=10
    • Maxb = 40.8,对应 l = 5 l=5 l=5
  • 但如果 S ( 1 , q ) S(1,q) S(1,q) 在某处小于0,并且之后一直小于0,情况就变复杂了

    比如我们将1.3的数据改动 2 个(8 和 9 改为 -62.2 和 44.2),如下表:


    由图表知,Maxf 出现在 r = 6 r=6 r=6 的位置,Maxb 出现在 l = 9 l=9 l=9 的位置:

    • 右边界在左边界的左边,算法错误
    • 原本 [9, 10] 之间元素和为 49.6,是真正的 Maxf
    • 在累加了前 8 个元素之后和小于0,并一直小于0,因此没找到
  • 对步骤2、3进行改进

    • 步骤2:先把左边界 p p p 固定在第一个大于0的位置,令 q = p , p + 1 , . . . , K q=p,p+1,...,K q=p,p+1,...,K,计算 Maxfr

      如果算到 S ( p , q ) < 0 S(p,q)<0 S(p,q)<0,从 q q q 开始反向计算 Maxb,此时可以确定从第1个数到第 q q q 个数的和最大区间 [ l 1 , r 1 ] [l_{1}, r_{1}] [l1​,r1​](这里 l 1 = p l_{1}=p l1​=p)和 Max1

    • 步骤3:从 q + 1 q+1 q+1 开始向后扫描,重复步骤2,可能在算到某个 q ′ q' q′ 时,又会出现 S ( q + 1 , q ′ ) < 0 S(q+1,q')<0 S(q+1,q′)<0,以此可以得到第二个局部最大区间 [ l 2 , r 2 ] [l_{2}, r_{2}] [l2​,r2​] 和 Max2,接着确定从头开始到 q ′ q' q′ 的和最大区间

      比较 Max1Max2Max1 + Max2 + S ( l 1 , r 2 ) S(l_{1}, r_{2}) S(l1​,r2​),发现从头开始到 q ′ q' q′ 的和最大区间是 [ l 1 , r 1 ] [l_{1}, r_{1}] [l1​,r1​] 或 [ l 2 , r 2 ] [l_{2}, r_{2}] [l2​,r2​],每次保留更大的区间到中间变量 Max 和 [ l , r ] [l,r] [l,r] 中即可

    • 接着,步骤4用步骤3的方法,向后扫描完整个序列,更新完 Max
    • 最后,得到的局部和最大区间 [ l , r ] [l,r] [l,r],就是全局和最大区间
  • 小结一下序列在累计求和时出现的两种情况,如下两图所示:


    简单来说,就是 全局和最大区间 = Max {大于0的局部和最大区间}
  • 综上,这个算法只需要 将整个序列扫描两遍(正反),时间复杂度为 O ( n ) \bm{O(n)} O(n)

【思考题 1.3.1】

【问】将例题1.3的线性复杂度算法写成伪代码。

【答】参考了网络大牛的做法,加上了一些自己的理解:

def maxSubArray(arr):# 算到 S(p,q)<0 时,得到的最大局部区间和sum_tmp = 0# 当前最大总和初始化sum_max = arr[0]# 左边界初始化left = 0# 右边界初始化right = 0# 正向局部左边界初始化left_tmp = 0# 完整遍历序列for i in range(len(arr)):# 正向累加sum_tmp += arr[i]# 局部最大 > 当前最大if sum_tmp > sum_max:# 更新当前最大值sum_max = sum_tmp# 更新右边界值 rright = i# 更新左边界值 l(上一轮S<0的局部左边界值)left = left_tmp# 局部总和值小于0,跳过当前数(简化)if sum_tmp < 0:# 更新局部和sum_tmp = 0# 更新局部左边界值left_tmp = i + 1# 返回全局最大值和全局总和最大区间return sum_max, arr[left:right + 1]if __name__ == '__main__':arr = [1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -62.2, 44.2, 5.4, -7.8, 1.1, -4.9]# (49.6, [44.2, 5.4])print(maxSubArray(arr))

结测试,例题1.3的两种情况均验证成功。

【思考题 1.3.2】

【问】在一个数组中寻找一个区间,使得区间内的数字之和等于某个事先给定的数字。

【答】参考了网络大牛的做法:

def subarraySum(arr, T):'''1. 用字典代替哈希表(当子序列和已经存在于字典中时,可直接跳过)2. 循环计算子序列和 S(1, i) 时,可以寻找 S(1, i) - T 是否在字典中,找到的key值满足:对字典中已存在的 S(1, j),j < i'''hash_dict = {}temp_sum = 0for i in range(0, len(arr), 1):temp_sum += arr[i]if temp_sum == T:return (0, i)if temp_sum - T in hash_dict.keys():return (has_dict[temp_sum - T] + 1, i)if temp_sum in hash_dict.keys():continueelse:hash_dict[temp_sum] = i

【思考题 1.3.3】

【问】在一个二维矩阵中,寻找一个矩形的区域,使其中的数字之和达到最大值。

【答】参考了网络大牛的做法:

def getMaxMatrix(matrix):r1 = c1 = r2 = c2 = 0max_sum_matrix = matrix[0][0]for i in range(0, len(matrix), 1):arr_list = [0] * len(matrix[0])for j in range(i, len(matrix), 1):# 将新的一行与原矩阵逐项累加,生成新的一维序列arr_list = [arr_list[k] + matrix[j][k] for k in range(0, len(arr_list), 1)](temp_max_sum, temp_left, temp_right) = self.maxSubArray(arr_list)if temp_max_sum > max_sum_matrix:max_sum_matrix = temp_max_sumc1 = temp_leftc2 = temp_rightr1 = ir2 = jreturn [r1, c1, r2, c2]

总结

  • 在计算机科学领域,从业者要想提高解决问题的能力,就需要不断培养对计算机的感觉。阅读例题1.3的四种解法,我们可以总结出如下建立感觉的方法:

    • 认识问题边界(至少扫描整个序列一次)
    • 优化算法(少做无用功)
    • 逆向思维(反向累加)

参考资料

  • 《计算之魂》第一章
  • https://zhuanlan.zhihu.com/p/476641538
  • 大牛的聊天记录

《计算之魂》阅读笔记 02相关推荐

  1. 人月神话阅读笔记02

    贵族专制,民主政治和系统设计: "现在让我们来处理具有浓厚感情色彩的问题--贵族统治和民主政治.结构师难道不是新贵?他们一些智力精英,专门来告诉可怜的实现人员如何工作?是否所有的创造性活动被 ...

  2. 人月神话贯彻执行_人月神话阅读笔记01

    本篇是人月神话阅读笔记的第一篇. 1-8章 1.焦油坑 焦油坑的意思说明了即使你足够强大,也无法摆脱束搏而沉到坑底. 可供大部分人使用的软件开发起来可不是一件简单的事情 乐趣与苦恼是这个行业避不开的话 ...

  3. 人月神话阅读笔记 03

    形式化定义仅仅用于外部功能,说明它们是什么. "今天,我比以往更加确信.概念完整性是产品质量的核心.拥有一位结构式是迈向概念完整性的最重要一步.这个原理不仅限于软件系统,它适用于所有的复杂事 ...

  4. 人月神话阅读笔记(二)

    今天对人月神话的正文部分进行了阅读,从人月神话这一部分中了解到缺乏合理的时间进度控制是造成滞后的主要原因,比其他任何事情影响的和还大,书中也对造成这种这种普遍灾难的原因进行了并进行了详细列举. 首先, ...

  5. 1483_人月神话阅读笔记_外科手术队伍

    全部学习汇总: GreyZhang/The_Mythical_Man_Month: My reading notes of The Mythical Man-Month. (github.com) 继 ...

  6. 人月神话阅读笔记07

    第1章 焦油坑       焦油坑的意思说明了即使你足够强大,也无法摆脱束搏而沉到坑底.IT项目也是这样,不论是开发大型软件系统还是小型项目,都会遇到诸多复杂的问题和影响因素,项目本身就是一个足够复杂 ...

  7. 人月神话阅读笔记01

    本书作者作为一个经验丰富的软件项目管理者提供给我们很多发人深省的观点.先从书名"人月神话"开始探讨,人月即早期用来度量软件开发工作量的一个单位.具体为将每个人每月的工作量作为一个基 ...

  8. 05人月神话阅读笔记

    人月神话这本书中主要在项目管理方面开刀的项目估算,组织结构和人员角色安排,团队建设和沟通,历史数据积累和建模,软件开发的方法论,风险和问题管理等相关的内容:在软件工程方面可以看到构架设计保证概念完整性 ...

  9. 1480_人月神话阅读笔记_开篇

    全部信息汇总: GreyZhang/The_Mythical_Man_Month: My reading notes of The Mythical Man-Month. (github.com)e ...

  10. 1514_人月神话阅读笔记_20年后的人月神话_上篇

    全部学习汇总: GreyZhang/The_Mythical_Man_Month: My reading notes of The Mythical Man-Month. (github.com) 这 ...

最新文章

  1. Linux文件内容查看相关命令
  2. Domino R8.5相关链接
  3. 14.结构体struct.rs
  4. Java架构师必须知道的 6 大设计原则
  5. Quartz简单实例
  6. java倒计时跳出窗口_java倒计时弹出框
  7. fiddler修改支付金额_不容忽视的记账工具:支付宝记账
  8. golang mysql连接池原理_redis mysql 连接池 之 golang 实现
  9. word2vec中的数学模型
  10. px、pt、em、rem 的区别
  11. xmpppy获取服务器版本信息,为什么XMPP? - 今幕明的个人页面 - OSCHINA - 中文开源技术交流社区...
  12. 节俭,是一种了不起的能力
  13. 极简毕业设计答辩PPT模板
  14. C++设计模式基础和模式设计基本原则
  15. 阿里巴巴编码规范认证考试总结
  16. PPG_Projector第一种方法:FindA
  17. FM/AM收音机原理
  18. 个人卖云服务器需要什么证,云服务器个人使用能做什么 云服务器要不要个人认证...
  19. 【报告分享】2020中国代餐食品市场分析报告-IT桔子(附下载)
  20. 搭建React项目,超简单教程

热门文章

  1. 方舟编译器编写鸿蒙软件,华为开发者大会,鸿蒙系统方舟编译器开源
  2. RK3288 android 7.1 EMMC 问题导致卡死在开机logo 界面
  3. 构建多层防御应对勒索软件威胁
  4. 【原】Windows XP SP3 IIS 安装包下载
  5. Intuition out of counter-intuition
  6. 设计模式-策略模式替换if else
  7. lol国服维护可以玩别的服务器吗,LOL手游部分服务器可以裸连,国服确定6月1上线,美服直接哭出声!...
  8. chrome:0x80040154无法更新 谷歌浏览器官方简体中文下载地址
  9. 热释电红外报警c语言程序,基于51单片机的热释电红外检测报警器程序
  10. 传奇世界彩虹引擎沙城皇宫不卡位地图补丁