慢啃《编程珠玑》【持续更新ing……】

第一部分 基础

第1章 开篇

首章介绍了一个历史问题:

怎样给一个磁盘文件排序?更具体地说,是对一个最多包含1千万条记录,每条记录都是7位整数的文件进行排序,而且只有1MB的内存可以使用。

借助这个问题,引出了作者的思考和对读者的启示:

  • 做一个”懒”工程师。处理问题时,即便是一看上去就知道解法的问题,不要马上动手编写代码,花些时间去明确问题,抽象问题模型,结合问题的特点去分析,等灵光一现时再动手。作者在解决上题时,首先对问题进行了数学定义,考虑到问题中数据的特点以向量的方式解决了问题,并且时间复杂度和空间复杂度都较低。这样的方式不仅能收到更好的编码效果,考虑的情况会更加完善,调试起来的时间也会缩短,而且经过深度思考再动手的印象要深刻的多,等之后再遇到类似的问题时,马上就会涌现解决问题的思路,这样才能积累真正的经验。
  • 时间和空间可以是双赢的。确实很多时候也许我们会在时间和空间之间做折中的选择,但是这种折中一定应该是在我们仔细分析了某方面不能再有改善的情况下,而很多情况下因为自己算法能力的不足和思维能力的局限,也许自己的算法可以在时间和空间双重优化。这就要求一方面自己要深度思考当前算法的优化空间,另一方面可以向更优秀的人请教是否有完全不同但是可以达到更好效果的方法或者对自己算法的建议,如果确实确定要做折中处理的话,要明确问题对时间和空间要求的严格性和最大容忍限度,然后在合理牺牲某方面的前提下向预定目标靠拢。
  • 简单的设计。“设计者确定其设计已经达到了完美的标准不是不能再增加任何东西,而是不能再减少任何东西”,在满足要求的前提下尽量让设计简洁,有利于今后的扩展和排错,“大道至简“,设计需要的也许更多是简洁的美。

——部分内容摘自coding小世界_会敲代码的咩_CSDN博客-android,算法,数据结构领域博主

第二章 啊哈!算法 

第2章围绕3个问题展开,分别引入了二分搜索,基本操作(reverse)和排序。

先看第一个问题

给定一个包含32位整数的顺序文件,它至多只能包含40亿个这样的整数, 并且整数的次序是随机的。请查找一个此文件中不存在的32位整数。 在有足够主存的情况下,你会如何解决这个问题? 如果你可以使用若干外部临时文件,但可用主存却只有上百字节, 你会如何解决这个问题?

 我的思考:

首先明确,一定存在整数不在文件中,因为32位整数最多可以表示的数字是2^32=4294967296>4000000000。

  • 在内存充足的情况下,可以用第一章位向量的做法:初始化2^32个位为0,每读到一个数字就将对应下标的位数组的值置为1,最后统计值仍为0的元素就是没有出现过的整数。这样的做法时间复杂度是O(2^n),空间占用大约为O(2^n/8)B,当n=32时占用空间大小约为2^n/8/2^10/2^10=512MB.

再来看作者的妙招,核心思想是二分搜索(我的思考更像是线性搜索)

第二个问题,直接上作者的方法,非常巧妙——不需要额外空间,善用一些基本操作。

 请将一个具有n个元素的一维向量向左旋转i个位置。例如,假设n=8,i=3, 那么向量abcdefgh旋转之后得到向量defghabc。

void rotate(string str,int i){
    int len = str.length();
    if(len > 0 && i%len!=0){
        reverse(str[0],str[i-1]); // cbadefgh
        reverse(str[i],str[n-1]); // cbahgfed
        reverse(str[0],str[n-1]); // defghabc
    }
    cout<<str<<endl;
}

第三个问题

 给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”, “stop”,“tops”相互之间都是由另一个词的各个字母改变序列而构成的, 因此这些词相互之间就是变位词。

 实话说,我对这个问题无从下手,看了书也不太明白。感谢coding小世界_会敲代码的咩_CSDN博客-android,算法,数据结构领域博主,他的讲解很详细,让我明白了这个问题。以下附上他的笔记+我的思考。

考虑一下变位词的定义,它们有共通点:组成字母集合相同。也就是说我们只要为每个单词选择标识,再聚集相同标识的单词就好了。标识可以是按所包含字母顺序排列的单词,比如”pots”,”stop”,”tops”的标识都是 “opst”。

本章感悟

--原理:

  1.排序。排序最显而易见的用处,是产生有序的输出

  2.二分搜索。该算法在有序表中查找元素时极为高效,并且可用于内存排序或磁盘排序缺陷:需要整个表已知,并事先排好序。

--思路:

  1.有的事情看上去很复杂,但经过仔细分析后,可能问题一下子就变得很简单了。而即便是真正困难的问题,也不见得解决方法有多么复杂。很多时候,我们缺乏的就是拨开问题迷雾、明确问题模型、把握问题本质的能力

  2.很多算法都是由一些基本操作组合而成的,所以我们要做的,一方面是扩展自己的算法基础——多掌握一些基本的算法思想和高级数据结构;另一方面就是面对实际问题时能提取出问题的抽象模型,找到适合的基本操作,再结合问题的特点,对算法进行改进甚至另辟蹊径,以一种巧妙的方式更简单地解决问题。

 ----------------------------------以上更新于2022-02-17--------------------------------------

第三章  数据决定程序结构

这一章没有讲算法的具体内容,作者列举了几个因使用错误的数据结构导致的问题,并给出建议。

整理了一些作者的tips,下附上笔记及原文:

笔记:

原文:

下面是退回起点进行思考时的几条原则:

  • 使用数组重新编写重复代码。冗长的相似代码常常可以使用最简单的数据结构—— 数组来更好地表述。

  • 封装复杂结构。当需要非常复杂的数据结构时,使用抽象术语进行定义, 并将操作表示为类。

  • 尽可能使用高级工具。超文本,名字-值对,电子表格,数据库, 编程语言等都是特定问题领域中的强大的工具。

  • 从数据得出程序的结构。在动手编写代码之前,优秀的程序员会彻底理解输入, 输出和中间数据结构,并围绕这些结构创建程序。

本章感悟

记得本科期间看到一句话:程序=数据结构+算法,优化数据结构,对优化代码有着重要作用。学完本章,在日后的编程中,如果遇到重复性强的代码,我会多尝试使用数组,以提高代码的通用性和兼容性。

----------------------------------以上更新于2022-02-22-------------------------------------- 

第四章 编写正确的程序

本章以二分搜索为例子,演示了算法验证的“三步曲”:初始化、保持和终止。

初始化:循环初次执行的时候不变式为真。
保持:如果在某次迭代开始的时候以及循环体执行的时候,不变式都为真,那么,循环体执行完毕的时候不变式依然为真。
终止:循环能够终止,并且能够得到期望的结果。

程序编写完成后,测试的重要性并不亚于编码本身

由于思维逻辑的漏洞、程序编写时的手误,都有可能造成程序Bug,而测试正是发现Bug的过程,也只有发现了Bug,找到Bug的原因才能彻底解决Bug,保证程序的正确性和健壮性。
程序员也应该充当测试人员的一部分角色,证明所用算法的正确性(实际中并不用一字一句地去证明),用全面的测试用例验证程序结果,尽早在自己手中发现Bug,虽然实际工作中程序员不需要也没有时间像测试人员一样逐个地去考察程序的各个指标,但一些显而易见的逻辑漏洞和性能问题最好还是在提测之前主动check一下才好。由于实际工作中,提测、测试、提Bug单的流程会浪费一定的时间,而且突然的一个Bug单也会打乱现在工作的节奏,所以这样可以在一定程度上缩短程序开发的总周期,也可以增加别人对自己编程能力的肯定。试想如果你不时就会听到测试人员对某位开发人员说“某某地方程序结果又有些不对啊“,想必这将会大大降低对这位开发者的信任程度。 

----------------------------------以上更新于2022-03-03--------------------------------------

第五章 编程小事

----------------------------------以上更新于2022-03-11--------------------------------------

第二部分 性能 

第六章 程序性能分析  

----------------------------------以上更新于2022-03-14--------------------------------------

猜你喜欢

转载自blog.csdn.net/weixin_43594181/article/details/122978919
今日推荐