【编程珠玑】

想不到薄薄的200+页,居然能让我如此地狂笑不止,暗叹妙哉,手不释卷。对于学习计算机算法而言,有一点基础之后就可以翻翻这本“大师系列”的课外读物了。先说说里面的结构篇章,然后再说说它吸引我的地方。第一部分介绍了一些基础知识,但是读起来非常有趣,有些算法简直让人惊叹不已,而后面的编程练习也十分具有挑战性,这部分分为算法、数据结构、编写正确的程序、编程的次要问题。一一介绍了许多软件开发中非常有用的方法和技巧:数据表现形式、构造数据、使用断言、记时。第二部分讲了性能分析、算法设计思想、如何多层级地提高性能。还没有看完,所以要慢慢看,看完再敲些代码。里面介绍的都是一些在实践中非常有用的技巧和工具,比如搭建脚手架去测试函数等等,而且以一个problem solver的角度去写程序,真正地是程序员应该学习的“葵花宝典”级的读物。
 
【解决问题的角度及一般方法】
1、问题是什么——问题的精确陈述
2、问题有什么可以利用的特征
3、怎样的数据表示
4、怎样的算法
5、验证
pascal之父说过,程序=数据结构+算法,那么我们解决一个现实中的问题,首先需要抽象出问题的本质,再抓住关键的性质建立数据模型,然后再对数据模型设计合适的算法。我们大多数人会觉得算法应该是一连串离散的解题步骤,就像数学里面的解题步骤一样,但我们不妨从问题的角度去审视,算法可以说是解决整个问题的办法,而不仅仅是有了数据结构后怎样算的问题。不同的数据结构,算法的思想可能不一样,其实不一样的更多在于细节实现不一样。比如实现一个栈,用数组可以实现一个,用链表也可以实现一个,封装后的操作都是一样的(压栈出栈),但是其实现细节是不同的,一个是用指针,一个是下标,但是归根到底思想是一样的,于是我们叫他们是同一算法的不同实现。有时候要解决一个问题,有一般的方法,或穷举,或贪心,或动态规划等,但是有时候我们也能利用到数据的一些特性来做特别处理,就像书中开篇提出来的例子一样——用位串的方法来排序,最终获得的解法占用空间小,速度也是一般排序算法没得比的。这或许就是哲学书里面提到的“具体问题具体分析”吧。
 
【算法设计的思想精髓】
1、分治(divided and conquer)
分治的方法就是将大事化小,小事化了,以达到大事化了的目的。分治的典型应用就是归并和快速排序,将一个大问题化成若干个规模更小的小问题,将小问题逐一击破,最后整合出原问题的答案。人解决问题的时候往往是有自己内在的流程,往往也是这样解决问题的(例如将一个软件工程分派任务),对于计算机而言,将问题分成无耦合的若干个问题,也是能完美运用分治的手法。
2、递归
与分治相随的是递归,递归是将原问题划分为和原问题性质相同的一些子问题。就以归并而言,算法中将原数组一分为二,先对前一段也是进行这样的操作,如此递归下去,直到只有一个元素无法再分就逐层返回,再对后一段同样操作,最后返回两段合并后的结果。更加典型的要数二叉查找树了,大于根则向右找,小于根就向左找。递归是一个一劳永逸的方法,同一算法既适用于原问题,又适用于子问题,而子问题的解决最终达成原问题的解决(当然递归总有一个结束的地方)。 这里总结了一些平常使用递归的例子。
 
算法设计的思想中分治与递归无疑是最闪光的,它们也相当具有启发性,很多很好的算法身上都可以看到它们的影子(如快排)。但是我发现,更好的算法往往更加复杂,复杂得难以理解(如字符串旋转的杂耍算法),只是在眼花缭乱之际,它就已经把工作做完了,故而,仅有分治与递归还不够。
 
 
【一些有用的技术】
1、断言
断言就是某个语句执行完后,某些变量必须要满足的条件。比如:
int i=0;
if(i==0)
{
    //i一定是等于0
    i++;
    //执行后,i一定是1
}
当然这个例子不具有实际意义,但是断言可以保证你的程序不会犯下逻辑错误。书中用二分查找这个算法作为例子,演示了一遍怎样利用断言写一个没有bug的算法。写二分查找要小心谨慎,因为一不考虑周全,可能就会写错。有了断言,你就可以确保在条件满足的情况下执行什么样的操作。
 
2、封底计算
封底运算直白地来说就是最大(小)到多少,我们算法中的复杂度记号f(n)=O(g(n))也是一个封底运算,即复杂度最大不会超过g(n)。文中用了很多例子来训练我们估算封底的能力,因为我们要的是针对有意义的例子,而不是宇宙中所有的可能情况。比如一条河流一秒的流量,绕地飞船的最大速度(第一宇宙速度)或者饶地球一圈最短多长时间(这个高中时期计算过)等等。掌握封底运算,我们就能很理性地判别那些东西可信,哪些东西不可信。
 
【一些有趣的例子】
 
1、开篇的例子——用位串来实现排序
 
书中开篇讲了一个很有趣又很有启发意义的例子,先看问题的精确描述:
输入:一个input文件,至多包含n个正整数,n个正整数每个最多出现一次,n=10^7.
输出:输出这n个排列好顺序的数到output文件中。
解决办法:用一个长度为10^7的初始化为0的位串,如果该数出现了,在对应的位数上记1,到最后扫描完整个input文件,就已经将排序次序记录在位串里面了。输出时只要遍历一下位串的每一位就可以输出有序的数列了。
 
2、字符串旋转——杂耍般的算法
 

编程珠玑-葵花宝典_第1张图片

要旋转一个字符串,可以用递归的办法,先转置a部分,再转置b部分,再转置ab。具体的描述是这样的,a——>a',b——>b',(a'b')——>(a'b')' = ba. 可以用手试试,最后一步是要两手并在一起往前滚。真是一个很神奇的算法,可以用如下语句描述。
reverse(1,5);
reverse(6,10);
reverse(1,10);
 
【本书的风格】
《编程珠玑》写作风格幽默诙谐,常常引得我发笑,但是又不乏严谨理性,对于每个例子都讲解得很透彻。不得不说的是后面的一些练习,真正能够训练我们解决问题的能力,可惜我没有做多少,将来肯定还要重读此书,或许还会读其后续版本。无论如何我都强烈推荐学了C语言将要学习算法与数据结构的同学看看这本书,可以获益良多。
 
by bibodeng 2012-07-16   原载于 bibodeng think beyond