第二章 O(n²)
数据结构课在晚上七点,教室在四楼,窗户正对着食堂后厨的排风扇。
林知行走进去的时候,屋里已经坐了大半。前三排空着,中间几排稀稀拉拉有人,后排挤成一团。他选了中间偏右靠窗的位置,把书包扔在桌上,掏出手机。
方小满还没来。
讲台上,一个中年男人正在调试投影仪。他穿着洗得发白的格子衬衫,袖子卷到手肘,头发稀疏,但梳得一丝不苟。林知行认得他,姓吴,是计算机系的老教师,教了二十多年数据结构,据说早年在研究所干过,后来不知道为什么调到大专来。
吴老师清了清嗓子,投影幕布亮起来。
"今天我们讲第三章,算法的时间复杂度分析。"
他的声音平淡,像在念一份过期报纸。
"首先,什么是时间复杂度?简单说,就是算法执行时间与输入规模之间的增长关系。我们用大O表示法来描述……"
林知行本来没打算听。他靠在椅背上,目光落在窗外。食堂后厨的排风扇嗡嗡转着,把油烟味搅碎了往教室里灌。他皱了皱鼻子,把窗户推开一条缝。
"比如,一个简单的循环,从1到n,执行n次,我们说它的时间复杂度是O(n)。"
吴老师在黑板上写下O(n),字迹工整得像打印出来的。
"如果是嵌套循环,外层n次,内层也是n次,总执行次数是n乘以n,也就是n²,时间复杂度是O(n²)。"
他又写下O(n²)。
林知行本来已经准备低头玩手机了,但这两个符号让他停住了。
O(n)。
O(n²)。
他盯着黑板,脑子里突然冒出一个念头:如果人生是一道算法,那他现在的时间复杂度是多少?
"再比如,"吴老师继续讲,"如果一个问题需要穷举所有可能的子集,那时间复杂度就是O(2^n)。指数级增长,当n稍微大一点,计算量就会爆炸。"
O(2^n)。
林知行的手指无意识地敲着桌面。
指数爆炸。
他想起今天下午在宿舍里做的那个穷举分析——专升本、考公、找工作、躺平——每一条路都试一遍,每一种可能性都评估一次,那不就是O(2^n)吗?
n是他面临的选项数量,2^n是所有可能的组合。
当n等于4的时候,2^n等于16。
当n等于10的时候,2^n等于1024。
当n等于20的时候,2^n等于一百万。
他的人生选项远不止4个,但即使是4个,穷举一遍也要评估16种组合。而每一种组合都需要时间、精力、金钱——他根本没有足够的资源去跑完这个算法。
"所以我们为什么要研究时间复杂度?"吴老师推了推眼镜,"因为同样的问题,用不同的算法,效率可能差几个数量级。一个O(n²)的算法和一个O(n log n)的算法,在输入规模大的时候,运行时间可能差几千倍。"
林知行坐直了身体。
O(n log n)。
他听说过这个,比O(n²)快,比O(n)慢,但具体是什么意思,他没搞清楚。
"举个例子,"吴老师转过身,在黑板上画了两条曲线,"假设n等于1000,O(n)的算法执行1000次,O(n²)的算法执行100万次,O(n log n)的算法执行大约1万次。同样是排序1000个数,用冒泡排序是O(n²),用快速排序是O(n log n),快100倍。"
100倍。
林知行脑子里嗡了一下。
同样的问题,换一种算法,效率差100倍。
那他的人生呢?
如果他现在用的是O(2^n)的穷举算法,那有没有可能存在一种O(n log n)甚至O(n)的算法,能让他用更少的时间、更少的资源,找到更好的出路?
他不知道。
但他第一次觉得,也许问题不是他不够努力,而是他用的算法太蠢了。
"当然,"吴老师喝了口茶,"时间复杂度只是一个理论上的估计。实际运行时间还跟硬件、编译器、输入数据的分布有关。但作为一个程序员,你首先要学会的,就是分析问题的复杂度,然后选择合适的算法。"
他顿了顿,补充道:"不会分析复杂度的程序员,就像不会看地图的司机,跑得再快也可能绕远路。"
教室里有人笑了一声,但很快又安静下来。
林知行没笑。他盯着黑板上那两条曲线,脑子里在转:
如果人生是一道算法题,那他的输入是什么?
是他的学历、家庭背景、能力、性格、运气?
输出是什么?
是他的收入、社会地位、成就感、幸福感?
约束条件是什么?
是时间、金钱、机会、他人的期待?
目标函数是什么?
是最大化收入?最大化社会地位?还是最大化某种他自己都说不清的东西?
他不知道。
但他知道,他现在用的算法是O(2^n)的穷举——把所有可能的路都试一遍,然后选最好的。
问题是,2^n太大了,他根本跑不完。
下课铃响的时候,林知行才发现自己整整一个半小时都在发呆。不是走神,是真正的思考——他把吴老师讲的每一个概念都映射到了自己的人生上。
O(n)是什么?
是线性增长。一条路走到黑,付出多少努力就得到多少回报。听起来很公平,但问题是,如果这条路本身就是错的,那走再远也是白费。
O(n²)是什么?
是平方级增长。每增加一个选项,复杂度就翻倍。他现在面临的选项越来越多——专升本、考公、找工作、考研、创业、躺平——每增加一个选项,穷举的计算量就指数级增长。
O(2^n)是什么?
是指数级增长。所有子集的组合。他人生的所有可能性组合在一起,就是一个天文数字。穷举一遍,比宇宙的年龄还长。
那O(log n)呢?
吴老师没讲这个,但林知行知道。O(log n)是对数级增长,每次把问题规模减半。二分查找就是O(log n)——在一百万个数里找一个数,只需要比较20次。
20次。
如果他的人生也能用二分查找的思路来解呢?
不是把所有可能的路都试一遍,而是每次都排除一半错误答案,用最少的尝试次数,找到最优解。
可能吗?
他不知道。
但这个念头像一颗种子,落进了他脑子里。
"走啦!"方小满不知道什么时候出现在他旁边,拍了拍他的肩膀,"你今天怎么了?上课这么认真?"
林知行回过神,发现教室里的人已经走得差不多了。吴老师正在收拾讲台,把粉笔头扔进盒子里。
"没什么。"他站起来,把手机塞进口袋。
"不对劲,"方小满凑近看了看他的脸,"你今天眼神不一样。"
"怎么不一样?"
"平时你上课都是死鱼眼,今天居然在发光。"方小满夸张地比划了一下,"是不是看上哪个女生了?"
"滚。"
林知行推开他,往教室外面走。方小满笑嘻嘻地跟上来,两人一前一后下了楼。
九月底的夜晚,空气里还残留着白天的闷热,但风已经带了一丝凉意。路灯把他们的影子拉得很长,叠在一起,又分开。
"你今天到底怎么了?"方小满又问了一遍。
林知行沉默了几秒,说:"我在想一个问题。"
"什么问题?"
"有没有可能存在一种O(log n)的人生算法。"
方小满愣了一下:"啥玩意儿?"
"就是……"林知行想了想,不知道怎么解释,"算了,你不懂。"
"你才不懂!"方小满不服气,"我好歹也学过数据结构好吧,虽然没学会。O(log n)是吧?就是那个……二分查找?"
"对。"
"你用二分查找找人生出路?"方小满一脸困惑,"怎么找?把人生对半切,然后看看哪边是对的?"
林知行没接话。
方小满挠了挠头:"你是不是今天受什么刺激了?辅导员点名的事?还是走廊里那几个傻逼说的话?"
"都不是。"
"那是什么?"
林知行停下脚步,转过身看着方小满。路灯从他背后照过来,把他的脸藏在阴影里。
"我只是觉得,"他说,"也许我一直在用错误的算法跑人生。"
方小满张了张嘴,想说什么,但最后只是拍了拍他的肩膀。
"走吧,回宿舍。"他说,"不管什么算法,先回去洗个澡,明天再说。"
林知行点了点头,继续往前走。
回到宿舍,方小满去洗澡,林知行坐在书桌前,打开了台灯。
他从抽屉里翻出一个草稿本,是上学期数据结构课发的,一直没用过。他翻开第一页,拿起笔,开始写。
输入:
- 学历:大专,计算机应用专业
- 年龄:22岁
- 家庭:普通工薪阶层,父亲货车司机,母亲超市收银员
- 能力:编程基础一般,但逻辑思维较强
- 资源:时间有限,金钱有限,人脉几乎为零
输出:
- 理想状态:找到一份体面的工作,收入稳定,有发展空间
- 底线状态:能养活自己,不啃老
约束条件:
- 时间:距离毕业不到一年
- 金钱:每月生活费1500,存款约3000
- 机会:专升本录取率约10%,校招企业质量差
- 社会压力:学历歧视、家庭期望、同龄人比较
当前算法: 穷举所有可能的路径,逐一评估,选择最优解。
复杂度: O(2^n),其中n为可选路径数量。
他盯着这些字看了很久,然后在下面写了一行:
问题:是否存在更优的算法?
笔尖在纸上停顿,墨水洇开一个小圆点。
他想了想,又写:
假设1: 如果能找到一个"启发式函数",能快速评估每条路径的优劣,而不需要穷举所有细节,那复杂度可以降到O(n)甚至O(log n)。
假设2: 如果能找到一个"工具",能帮他快速生成和评估方案,而不是手动一个个试,那复杂度也可以大幅降低。
假设3: 如果他的"输入数据"本身就有问题——比如学历太低——那再好的算法也没用。必须先改变输入。
他放下笔,靠在椅背上,盯着天花板。
方小满洗完澡出来,看见他坐在那里发呆,问:"又在想什么?"
"在想我的人生算法。"林知行说。
"你魔怔了。"方小满擦着头发,"明天再想呗,先睡觉。"
"你先睡。"
方小满摇了摇头,爬上床,很快就打起了呼噜。
林知行还坐在那里。
宿舍的灯关了,只有台灯还亮着,把他的影子投在墙上。他拿起笔,在草稿纸的最后一行写了一句话:
是否存在O(log n)的人生路径?
写完之后,他盯着这行字看了很久。
然后,他合上草稿本,关掉台灯,躺在了床上。
黑暗里,他睁着眼睛,脑子里还在转。
O(n)、O(n²)、O(2^n)、O(log n)……
这些符号像咒语一样在他脑子里盘旋。
他不知道答案是什么。
但他第一次觉得,也许问题不是他不够努力,而是他一直在用错误的方式努力。
就像一个不会看地图的司机,跑得再快也可能绕远路。
那他现在需要的,不是跑得更快,而是找一张正确的地图。
或者,找一个会看地图的人。
或者,找一个能帮他画地图的工具。
他翻了个身,把脸埋进枕头里。
明天,他得好好研究一下,这个世界上有没有这样的工具。