路人丙:“小夕,你说学算法有什么用呢?”
小夕:“好玩呀。”
路人丙:“算法问题那么多,现查现用不就好了?”
小夕:“好咯,既然你诚心诚意的问了~小夕就大发慈悲的震惊你( ̄∇ ̄)”
为什喵要学算法
听小夕慢慢讲哦。好像现在在低端程序员的圈子里经常流传着“算法无用论”和“临时抱佛脚论”,好像在有的人眼里,编程就是一个经验活儿,甚至说体力活。小夕只能说,这种想法让人听了会很尴尬的,别人都不知道该如何劝他们。
如果一个人是半道转行计算机,有这种想法是无可厚非的。但是甚至一些名校出身的大学生都会这样想,小夕就觉得非常尴尬啦。
算法是什么呢?
小夕说,算法就是计算方法(已逃
学习算法的意义有很多,比如很现实的用算法框架解决某个问题(比如文件排序),再比如用算法将一个时间上不可能完成的任务变得可能(比如用算法优化优美的斐波那契问题),小夕不一一列举。其实整个人工智能领域,高性能计算领域,最终都能纳回到精简的算法大框架里去。一句话,算法是计算机的精华。
算法的究极打开方式
好像很多人学算法就是背过各种算法然后刷几道题,应付一下考试就过去了。事后工作多年也翻不开算法书几次。小夕只能说,╮(╯▽╰)╭哎。
小夕觉得,最重要的还是学习每个算法问题中的算法思想。要解决的算法问题是无穷无尽的,但是诸多问题背后的算法思想确是相同的。所以你生生的背过了“解决排序问题可以用选择排序、插入排序、归并排序、快速排序、堆排序巴拉巴拉”,那么你只是成功解决了一个算法问题。但是如果你在排序问题的归并排序方法中提炼出了divide-and-conquer思想(中文叫“分治”),那么恭喜你成功解决了一大堆的算法问题。
下面小夕就以计算最长公共子序列(以下简称FCS)这个经典算法问题来给大家深刻感受一下从算法问题中提炼算法思想的过程。
FCS问题
首先,序列嘛,可以简单理解成字符串。比如“tsinghua”就是一个8字符的序列。
子序列呢,就是由序列中若干字符,按原相对次序构成。
比如下图就是原序列(上)与它的一个子序列(下)。
注意上下的连线一定不能交叉哦。
而最长公共子序列,就是两个序列的所有相同子序列中最长的那个。
比如序列didactical和序列advantage的最长公共子序列为data,你绝对不可能找出4个以上字符的公共子序列的。
下面就以计算序列didactical和序列advantage的最长公共子序列为例,开始吧~
一个纯粹的思路
观察一下这个问题,如果从字符串中间去想的话会感觉一团糟,所以从两头想啊。
是不是一下子灵感来啦?对于didactical和advantage的开头,即d和a,那么只有三种情况:
1、 要么a在FCS中,且是第一位,而d一定不在FCS中。
2、 要么d在FCS中,且是第一位,而a一定不在FCS中。
3、 要么d和a均不在FCS的第一位。
哈?你想说d和a可不可以都在FCS的第一位?小夕救不了你了。。。
走到这里是不是就好像走到了一个分叉路口呢?每一种情况就是一条路。
想一下,进入每一条路后,其实此时又是一个新问题。比如进入情况1之后,我们就可以把第二个序列的第一个字母d给剔除掉了,所以就成了计算idactical和advantage的FCS问题了。
而计算这个新的FCS问题,与刚才的思路完全一样呀,依然从头上开始,对i和a分三种情况讨论,然后进入每一种情况后,又是一个新的FCS问题。
诶?这个套路是不是似曾相识?对!这特喵的不就是学编程语言时讲的递归调用嘛!既然每一次的套路都一样,那就说明完全可以用一个函数反复调用自身来解决问题!
是不是灵感爆发啦?只要两个序列的开头字母不同,那就不停的重复下去呗~那如果开头字母相同怎么办呢?这还用说呀!那开头的这个字母肯定是FCS中的一部分啊!然后把这个字母保存下来后,都剔掉继续递归调用呀~比如didactical和dvantage,你说开头的这个d是不是FCS中的!敢说不是,小夕。。。哼哼哼,小夕凶死你~
既然开头字母要么相同,要么不同,反正最终都会递归调用。这时再考虑什么呢?显然,我们只需要考虑遇到“空字符串”时该怎么办就好了。
显然,只要遭遇了空字符串,那么肯定就代表着这一条寻找公共子序列的路线结束了,我们就可以数一数在这条路线上遇到了多少次“首字母相同”的情况,把这些情况首尾接起来,不就是这个公共子序列嘛~
最后,我们把这好多个公共子序列摆出来,比一比哪个最长(咦?怎么感觉怪怪的),最长的那个不就是我们要的FCS嘛~
小夕炼金秘法
好啦~任务完成。睁大眼睛看着吧!小夕只演示一遍哦~
首先,我们开始的时候很踏实,从问题的起点开始,也就是从字符串的首字母开始,然后很踏实的分析每一种可能的情况。对于每一种可能情况都考虑在内,最后再考虑一下每种情况结束时的情况和处理方法。诶?这种做法叫什么?大声告诉小夕~~这不就是小学就学过的枚举法嘛~一种情况一种情况的列举,别说你小学的时候没做过这种事。当然啦,枚举法也叫蛮力法、暴力法,是一个东西。这就是本解法的最关键的算法思想啊~新算法思想,get!
诶?等等,再深入的挖掘一下,刚才我是不是说我们很踏实的分析每一种可能的情况呢?这里有没有算法思想呢?这就是divide-and-conquer思想啊!就是分治呀~将一个大问题,一分为二,甚至一分为三,然后分别取解决每一个分好的子问题,这种思想就是分治思想!get!想一想拿着这个思想,去扔到排序问题上去,是不是立刻发现你们当年死记硬背的归并排序一下子就出来啦??有木有很激动~
然后继续分析,我们哪里来的自信,去相信不停的递归调用下去会结束呢?万一死循环了呢?想一想哪来的自信呢~
有没有发现,我们每到达一个岔路时,最少会让一个子序列的字符数量减少1呐!专业的说法是“问题的规模一定会减小”!这样不停的递归调用下去(专业说法叫迭代),问题的规模早晚减小为0!这种思想提炼出来了没~起个名字,叫减而治之!再总结一下,如果你发现你的递归会让问题的规模一点点的减小,那么就可以解决问题!有了减而治之的思想,以后不就会刻意的寻找让问题规模减小的方法了嘛~而不是无头苍蝇瞎蒙啦。
还能不能挖出来其他算法思想呢?唔,小夕暂时没有啦~但是你看,一个FCS问题的一种解法,就最少包含了3+1个算法思想!(解决问题的途中用到了递归,也算思想吧QAQ)如果你事先拿着枚举法、分治法、减而治之、递归去扔到这个FCS问题上去,那这个问题秒解呀~当然啦,提炼出来思想后,要深刻消化哦!
但是聪明的喵喵也想到了,枚举法看起来开销好大呀,这得让计算机考虑多少种情况呀,累坏计算机宝宝了~于是,有没有办法让计算机大幅度减少考虑的情况呢?期待小夕的下一篇从思想讲算法的大作吧!
但是小夕写下去的动力就是你们的小红包呀(⁎⁍̴̛ᴗ⁍̴̛⁎)