这本书中对算法的概念没有提太多。手里有本小书,叫《程序语言的奥妙:算法解读》,是日本人写的,好像是给高中生看的。(⊙﹏⊙b汗,好像蛮对我水平)。里面对算法概念描写的很浅显易懂,总结如下:
1)“解决问题的处理步骤”。烹饪用的食谱,游戏的攻略,棋谱,等都符合这个描述,算法和它们的本质相同,都是“解决问题的处理步骤”。
2)“利用计算机解决问题的处理步骤”,就叫算法,英文Algorithm['ælgə'rɪðəm]。
3)算法是“古老”智慧的结晶,是程序的范本。正如菜谱不是菜一样,算法也不是程序。
2.2 数据结构与算法的关系
读后感:
不论数据结构,还是算法,都是前人的智慧结晶。这些的结晶产生,必然有相同的背景,那就是“利用计算机解决问题的过程中”。我琢磨这,在这个过程中,它们从设计和实现,都是分不开的。
2.4 算法的定义,摘“算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作”。
读后感:
1)算法的本质是描述,不是程序代码。
2)描述针对计算机指令的运行过程。所以描述可以使用具体的指令,或“可以转换为一个或多个指令”的自然语言。
2.5 算法的特性,摘“算法具有五个基本特性:输入、输出、有穷性、确定性和可行性”
笔记:
1)输入,可以没有。
2)输出,必须的,否则算个毛啊。
3)有穷性,从描述的角度来讲,能讲得完,从计算机运行的角度,程序能跑到头,从实际应用角度来说,要有个合理的、可以接受的边界。
在web上,如果排除网络传输速率的影响,一个网页反应的时间超过5秒,都难以忍受了;但如果是计算什么战略性武器的程序参数,跑上几个月相信也能接受吧。
4)确定性,要求描述不能有二义性。
比如“如a太大,则a=0”,谁知道a多大算是“太大”。
如果说算法的确定性是指特定的输入,对应着唯一的一条路径和结果,就不好理解了(如果我就想做一个随机抽奖程序的呢)。
5)可行性,“意味着算法可以转换为程序上机运行,并得到正确的结果”。可行性的着眼点在于“计算机能做”,约束是“得到正确的结果”。
如果算法描述是“a,b交换数值”,就可以转换为机器代码并正确运行。
可若是“若a<b,则炒个土豆丝”,目前好像没听过有这种能耐的计算机。
2.6 算法设计的要求
笔记:
1)前面的是算法的特性,就是一个算法之所以称得上一个算法的Feature.不满足其中的任何一条,都不能成为算法。就像一个名为菜谱的单子上,教人如何烹饪鹅卵石,那还能叫菜谱吗?
2)算法设计的要求,有个前提,就是要首先能称之为一个算法,即满足算法特性的要求。
3)算法设计要求包括:
# “正确定:算法的正确定性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到正确的答案”
层次:
*1 无语法错误
*2 对于合法的输入数据,能够产生满足要求的的输出
*3 对于非法的输入数据,能够产生满足规格的输出
*4 对于精心选择的,甚至刁难的测试数据,能够产生满足要求的输出结果
笔记:
1)看到“正确性”,突然感觉好像废话,算法特性不是包含这一条了吗?可是回去看看,算法的特性还真没包含这一条.......不对,算法特性的最后一条“可行性”,已经包含了正确性了。只是和设计的“正确性”要求相比,泛泛而谈,只要对就行,没有层次之分。
2)“语法错误”有些迷糊,算法不仅仅是“描述”吗,它又不是程序,怎么会有语法错误?那么这里的“语法错误”,也就是针对“描述语言”本身了,不论是指令还是可以转换为若干指令的自然语言,都是算法的描述语言。是语言,就会有语法错误。
# “可读性:算法设计的另一目的是为了便于阅读、理解和交流”
笔记:
1)反应在代码上的算法,阅读起来越容易,就说明算法本身的思路越清晰。——过去我常常反其道而行之。
2)算法的本质是描述。描述,就是为了给人看。给自己看,为了下一步的实际编码定下思路;给别人看,为了交流、讨论,很多时候得去学习前人的算法。
即使只有自己用,几天后、几个月后,因为业务量剧增而需要改进算法的时候,还是要先去理解以前的想法。就我自己的情况而言(编写代码的经验),比起阅读别人的东西,那种陌生感更为强烈——我写的这是什么啊!?
# "健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常货莫名其妙的结果"
笔记:
1)健壮性,总给我一副肌肉男的印象,实在和“软”件难以扯上关系,只有看了定义才能“哦”的一声恍然大悟。转身后,又陌生起来。
健壮性的英文是【robustness[ro'bʌstnɪs]稳定性;奖状性;】,为什么不取“稳定”一次来定义这个概念呢?
2)这让我想起了表单录入。那些数字、日期框经常需要验证输入格式,如果不验证,提交后就到出现出错页面。有时候懒得做这种“多余”的验证工作,指望用户能“对着错误页面低着头一脸羞愧的反思着自己录入的每个步骤、检查自己犯下了什么样愚蠢的错误”——事实证明,用户除了当面的求助和背后的骂娘外,几乎没有“认识错误和纠正错误”的能力。在这种客观事实的背景下,只能总结出这么一个结果:程序必须能对那些不合法的输入给予符合规格的恰当处理——即使你以醒目的方式或位置给予了提示!当然,这份责任就归于程序员了,进而归于程序员的职业修养——提到这个高度,觉得好接受了点呢。
# “时间效率高和存储量低”
笔记:
1)时间效率高,是相对而言。10秒钟,是快还是慢呢?那得要在相同问题、相同输入量,并且不同的算法之间进行比较。没准10秒钟能让全公司的人笔试一遍,也没准半个小时能全世界将目光聚集到一个人身上。
2)存储量低,就是内存的消耗。任务管理器里,可以看到每个程序所占内存大小,“直观”的感受那些程序从几K到几百M的内存占用——虽然知道他们和“算法的存储量”没有多大关系。
2.7 算法的效率度量方法
2.7.1 事后统计法。总之这个没什么可说的,不会采用这种方法来度量算法的效率。硬件、系统、编译器输入问题规模和内容,都决定了用“实际测试”来衡量算法效率是不切实际的。
2.7.2 事前分析估算,“在计算机程序编制之前,依据统计方法对算法进行估算”。
笔记:
1)“在计算机程序编制之前”。为啥书上在介绍算法步骤的计算时,都用的程序代码呢?如,其中第一行,明明执行了3次,为啥要说1次哇?难道分开声明,才能算是一次?
int i , sum = 0 , n = 100; /*执行1次*/ for(i = 1 ; i <= n ; i++){ /*执行n+1次*/ sum = sum+i ; /*执行n次*/ } printf("%d",sum); /*执行1次*/
2)在另一段代码里,直接把循环体内的两行代码看成执行1次,累计算成n*n次。这让我有点崩溃啊。
3)“......我们只关心它所实现的算法。这样,不计那些循环索引的递增和循环终止条件、变量声明、打印结果等操作,最终,在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤”。
好像懂了点——联系前面的算法定义,描述如何一个解决问题,就是描述“步骤的先后顺序”,而每一个步骤相较于基本指令的粒度大小并不重要,重要的是否处于同一个步骤。
如“初始化”步骤,就使用3条基本语句实现:int i ,sum=0,n=100; 而“输出”步骤,只由一条基本指令组成:printf("%d",sum)。
4)依然似懂非懂。在算法陈述所体现的“思路”与计算机基本指令之间,我被计算“执行的次数”绊倒过很多次了。因为最后要计算f(n),累加的次数不一样直接导致结果不同,至少跟书上不一样,这让我感到深深的挫败感。
5)现在再看,觉得自己真傻。人家说的执行一次,并非是把语句折算成计算机在某种层次上的动作、然后对这个动作进行计数,而是说那条或那些语句就执行了一遍而已——仅此而已!上面2)中,循环体内的两条语句,在每次循环中当然执行一遍,难道还能一次运行跑两遍!?郁闷哇,这么多年,怎么一直没想明白这个东西,我的理解能力真是.......喷血了。1)中的声明语句,就算分开写,那也是执行了一遍哇,555555.
6)突然明白,难怪算法的瓶颈一般都在循环那里。人手动敲出来的代码能有多长,成千上万已经了不得了,可现实执行起来,执行代码的次数远远超过源代码的行数。其中的循环,是产生这些步骤的“大户”。