现在的程序员做软件,很大程度上说其实是做集成的工作,移植的工作。很少有机会去做一个算法性的模块,也确实,涉及到算法的部分早有别人为我们实现好了,开源的,或者组件,对于开源的,一般我是很懒得去看代码,我一般是弄清楚接口定义,知道怎么用就到此为止了。记得大二的时候还写过完整的AVL 树源码,现在让我写,估计花的时间估计不会少吧。不过应该也不会有这种如果,网上有大把的源码可以下载,如果某位上传了他的源码,他应该不会期待使用的人能够想起他的名字吧。
算法,特别是专业性很强的算法,比如图,树,动态规划,现在一般很少有机会去从头写了吧,这也无可厚非。我从来没有打算去啃那块算法,要用了,赶快去找现成的,最多,我把原理搞清楚好了。
但是有时候看有些代码,虽然没有特别的算法思想在里面,都是些基本操作或很普通的功能任务,但总是感觉,代码本的流程上不是那么简洁,不是那么漂亮,性能有明显的改进余地。当然,自己的代码也很烂,只是自己是看不出来而已了。
我们不需要去实现算法,不代表我们不需要思考,代码思维的延续,计算机擅长做简单重复的事情,你想的越多,计算机需要做的就越少。(程序员的交流对象都是计算机,也难怪越来越傻)
看这样一个简单的问题,实现abcdefg -> defgabc ,对的,就是循环左移。如何实现,如何思考?
不需要算法吧,实际中很常见的问题吧。
问题定义: n个元素的有序集合,循环左移k个元素。
1. 很自然的思路:
开辟k个元素的空间,临时存放集合最左边的k个元素,然后把集合中剩余的元素每一个都提前K个位置,最后把临时空间中的k个元素放在最后。很简单,时间复杂度O(n).
如果有空间要求呢?也很容易,只开辟一个临时空间好了,先左移一位,然后重复做k次。只是时间上开销比较大,复杂度k*n,典型的时换空间。
其实时间与空间并不一定是此消彼长的关系,当代码很烂的时候,其实他们都有消的空间,也就是说,当算法远没有接近最优算法的时候,时间与空间都有削减的空间。
进一步的思考,你可能可以想出这个算法,想不出来也没关系,反正上面的自然算法一般情况下也够了,代码简洁清晰一点就好了。
2. 技巧性的算法
第二种算法《编程珠玑》称为杂技算法,我觉得是满贴切的,就是说,要下一些功夫,发现一些技巧,当然,有些不是像我这么笨的人也许并不需要花多少功夫,但对我来说,至少我一下子没想到。
不描述了,画一下吧,我总是认为图能够比文字表达得更全面,如果理清了内在的规律再用图来表达,往往能够使问题变得很明了。有时候看书,觉得,这么简单的问题,也拿来讲,还画个图在这里,其实,也许不是问题真的很简单,更不是自己多聪明,而是作者用心良苦啊--题外话了。
其实每一个元素的移位目的地都是明确的,显然,一个元素大小的临时储存点是必须的,从a开始吧,把a先暂时放到临时存储点,空出来的位置,立即用应该移到这里的元素补上,以此执行下去,把集合想象成一个圆形的循环队列(那么势必有取模操作),那么现在需要想清楚的就是何时结束移位的问题。
这种方法相对于第一种方法,稍微归纳了一下问题的规律
a. 每个元素移动的目的地是确定的
只是,这个规律仍然是比较表面的规律,不能使问题足够的简化。
3. 移动内存块的算法
想到这种方法稍微需要一点类似脑筋急转弯的感觉,也就是稍微要点灵感吧。归纳出问题的规律:
a. 实质上是把集合的前K个元素放到最后面
b. 如果把集合的最后K个元素与最前面K个元素交换,那么剩下的问题就是把新集合的前(n-k)个元素的前K个元素放到这(n-k)个元素的最后部分,问题又回到了原点,这(n-k)个元素形成的新集合成了问题本身新的输入。
那么,有了上面归纳的规律,递归自然就是最合适的解决手段了。(在这里还要区分k>n/2的问题)
我一直认为,如果问题可以归结为一组递归,那么,基本上这个解决方法就已经算是抓住了问题的本质了,对递归我是非常推崇的。我也曾经实践过用递归的方式来解析某一固定格式的TCP数据流,这个处理入口接收任意长度的TCP数据段长度。
如果说这种方法需要一点跳跃型的想法,那么下面这种方法,一般人在怎么跳,估计都很难想出来。
4. 求反的算法
我不知道这个算法所依据的内在规律,也不知道它所揭示的规律,只知道它很简单,应该是对的。
将n个元素看成两部分k, n-k.
将前一部分反序,将后一部分反序,再将新的集合整体反序。
在我看来,这纯属巧合,就好像我看 DH密钥交换算法一样,因为我无法证明,也没这个能力。
总结:
其实前两种算法都算是比较笨的办法,解决方法所依据的都是比较表面的规律,没有成功的简化问题。但实际上,我们解决问题用的最多的还是这种比较笨的办法,不过一般也够用了。第2种办法稍微有些技巧性,但是代价是,也许得思考更久一些,就像玩杂技一样,你得练不少时间。
后两种算法算是很巧妙的算法,找到了问题的比较深层次的规律,问题就简化了。就好像为了测灯泡的体积,爱迪生把灯泡泡在装满水的容器里,而不是用微积分来算一样。
至于最后一种方法,没有很多灵感是达不到那个境界的。
关于算法,很难说因为有了现成的实现而不去理会它,很难说简单的问题就不需要算法。不需要去钻研专业性的算法,不代表可以不去思考。而且,什么叫做专业性的算法,什么叫做非专业性的算法,其实不好界定,关键是一种思维方式吧,解决问题的方法,要么直观,要么简洁,或者高效,如果写出来的代码这几点一样都不占,那也就够烂的了。