《尘世之外》

第二卷-大学时代 · 第 045 章

第四十五章 算法

三十度。寝室里没有空调。

凉席是竹片的,竹片与竹片之间的缝隙积了汗——翻个身能听见汗在竹片缝里哧的一声,很轻,像小虫子踩了一脚水。床垫子上的人形汗印到了下午已经干了第三遍——出完汗被风扇吹干,干了再出,出了再干,竹席上留下一圈淡黄色的盐渍。衣服搭在椅背上的湿了一半,湿的那半贴着椅背,干了之后椅背上印出一个人形的轮廓。

王强把校服脱了穿着背心躺下铺,一只脚垂在床沿外面晃,脚趾上的汗像露水——每根脚趾头上都挂了一颗,风一吹就流下来。老高用课本扇风,扇两下停一下,停的时候喘一口气然后再扇。阿杰打游戏打到一半停了——鼠标在手里打滑,手心的汗把鼠标表面弄得黏手,光标在屏幕上画了一个不规则的圈,游戏里的角色在圈画完之前被怪打死了。他把鼠标在桌面上推了一下,很轻,没掷。

七月。暑假没回家的人在寝室里过夏天。风扇在摇头——吹左边停两秒,吹右边停两秒,热风在寝室里走一个来回又回来。风扇的嗡嗡声和暖气管走水的咕噜声不一样——暖气管的咕噜是间歇的,有节奏的,像心跳;风扇的嗡嗡是持续的,不带节奏的,像一只不知道停的虫。


《算法导论》。图书馆二楼技术区,7号架3层,从左数第11本。

书比上学期那本《数据结构》厚了两倍。封面红色,红色的边角被手摸得发白了,书脊上有一道竖的折痕——翻的人多了书脊就出现一道筋,筋把书分成两半,打开的时候书自然翻到那一页,像开了闸的水自然往低处流。

排序算法。冒泡排序:相邻两个元素比较,大的往后换。每扫一趟确定一个最大值放在最末——像水处理池里沉淀颗粒,最重的先沉到池底。选择排序:从头到尾扫一遍找到最小的放在最前面——像食堂打饭,排最前面的人先打。插入排序:从第二个开始,每个元素插到前面已排好序列的正确位置——像打牌理手牌,摸一张插一张,摸一张插一张。三种排序的本质是一件事——把元素移到正确位置。位置对了,序列就对了。和管网设计一样——每根管段选对管径,整张图的水头损失就最小。

然后是图论。Dijkstra算法——最短路径。

书上画了一个图:七个顶点,十条边,每条边上标着权重。从顶点A到顶点G,最短路径是什么?算法一步一步走:先找离A最近的,标记它;再从标记过的顶点出发,找离A第二近的,标记它;一直走到G。每一步只看眼前最近的,走完了回头看——走过的那条路就是最短路径。贪心算法。每一步选当前最优,不回头看。

我盯着这个算法看了两个小时。下午两点的图书馆,空调刚好,凉风从出风口往下灌,书页被吹得微微翘起来。我用手指按住书页看,手心出了汗,在书页边角留了一个指印。

因为管网也是这个问题。

城市给水管网:从水源出发,经过干管、联通管、支管,到达用户。每条管段有水头损失——损失就是权重。我要找的是什么?从水源到用户,水头损失最小的路径。这不是Dijkstra算法吗?Dijkstra算法找最短路径——每一步选择当前最优,走完回头看就是全局最优。管网优化就是找最优路径——每条管段选管径使得水头损失最小,选完了回头看就是全局最优方案。

程序从三百七十行变成了五百行。加了Dijkstra模块:把管网的节点和管段转化成图的顶点和边,水头损失作为权重,从水源节点开始,用Dijkstra算法走到每个用户节点,输出的是最小水头损失的路径。编译运行。屏幕上弹出一行:

最短路径: S -> D1 -> D3 -> J-7 -> J-12

总水头损失: 0.41 MPa

0.41。比上个月的0.47低了0.06——联通管加进去之后,水走了一条更短的路。

管网不只是管。管网是图。水不只是流。水流走最短的路径。算法找到了水的灵魂——水从高压流向低压,遇到分叉选择阻力小的那条路走。不是我在指挥水往哪流,是水自己会选。算法只是替水算出了它本来就会走的路。

我在笔记本上写了两行字:

"管网是图。最短路径是算法。"

写完把笔放下。笔在桌面上滚了半圈,滚到笔记本边缘停住了。


设计院实习。两周。

画CAD。选管径。算压力。每天八小时坐在电脑前面,手指在键盘上敲参数,眼睛在屏幕上看管网图。管网图是国家标准图集里的通用模板——模板一样,参数不同。DN100的管段、DN150的管段、DN200的管段,三根管段的管径标在一起,中间用弯头连着,弯头的角度是90度,弧线的半径是管径的1.5倍。画完了标注,保存,打印。下一张图换一组参数再画一次。

每天算的压力都有标准公式。达西-魏斯巴赫,连续性方程,伯努利方程——三个公式反复用。数据变了,公式没变。参数不同,模板相同。和程序里的for循环一样——换了变量,循环体不变。

带我的工程师姓周。五十多岁,头发花白,戴金属框眼镜,镜框左边有一条焊缝,焊缝上的银漆掉了露出铜色——像一根笔直的疤。他看图的时候把眼镜推到额头上,从镜框上方看——两个镜片架在额头上方像两扇没关的窗。看完再把眼镜拉下来拉到鼻梁上,两扇窗关了,他开始说话。

他翻了三张我画的图,翻到第四张的时候停了。手指点在J-6到J-7的管径标注上——DN150。

"你用程序算的?"

"嗯。"

"算出来150?原图标的200。"

"流量36方每小时,流速1.2,150够。"

他没接话。把图纸卷起来,卷的时候手指在图纸上划了一下,指甲在牛面纸上留了一道白印。站起来走到窗边,背对着我说:

"你脑子活。"

然后他转过身,手搭在窗台上。窗台上的砂灰很厚,他用拇指碾了一下,砂灰在他指纹里嵌了一条线。

"但给排水的活不是脑子活就行——还得有经验。程序能算管径,算不了管子十年之后结的垢。DN150现在够用,十年后水质硬了结了垢,内径变成DN130,流速从1.2变1.6,噪音大、水锤大、用户投诉。原图标200不是算不出150——是给未来留余量。"

他说"经验"两个字的时候停了一下。不是在斟酌该不该说,是在把这两个字放重了一点,像在图纸上标注重点尺寸的时候把数字写大一号。

"经验这个东西——"他拉开椅子坐下来,椅子腿在地上拖了一声。"是时间在管壁上结的垢。你的程序算的是新管子。我的图画的是管子用了十年之后的样子。十年之后DN150变成DN130,DN200变成DN180——我的图上留了余量,你的程序没有。"

他站起来走了。图纸留在制图板上,卷成了一筒,纸筒口的绳子系了两圈——系绳的手法很熟练,一圈缠两圈锁,和他画管网标注一样规整。绘图室里只有日光灯嗡嗡响和水龙头的滴水声——滴、滴——每两秒落一滴,落在不锈钢水槽里,声音像有人在不紧不慢地敲一面很小的鼓。

我看着图纸上的DN200。周工刚才手指点过的那一段,指甲印还在——浅浅的一条白痕,压在管径标注的旁边,像一枚临时的戳记。他说的垢我看不见,程序也看不见。新管子里面是亮的——水在里面跑的时候不会留下痕迹。但用了十年之后呢?管壁上会结一层水垢,垢是硬的、灰的、粗糙的。水在结了垢的管子里跑,阻力大了,流速变了,压力低了——程序算不出的东西,时间替你算。

孙工上半年说过同样的话。程序能算管径,算不了地基沉降。现在周工又说了一遍。两个人在不同时间不同地点说了同一件事:代码能处理数据,代码处理不了时间。


回到寝室。王强不在——他回家了,回家帮他爸修开春积攒的车。铺子里攒了一冬天的毛病等他回去一起修:刹车片磨没了的、雨刮冻断的、电瓶亏电起不着的。他走之前把枕头底下的工具盒留给了我——"寝室东西坏了你先顶一下,不行就等我回来。"螺丝刀、尖嘴钳、生料带、胶布,排成一排。每一件都在该在的位置上。

夜里两点醒。风扇在摇头——吹左边停两秒,吹右边停两秒,热风在寝室里走一个来回又回来。床垫子上的汗已经干了又出,出了又干,竹席上有人形的盐渍。窗外有虫叫了——哈尔滨七月的虫比广西的少,但有几只叫得尖,像用指甲弹钢丝。

翻身坐起来。笔记本在枕头旁边,蓝色钢笔插在本子中间当书签。翻到今天写的那两行——

"管网是图。最短路径是算法。"

在上面加了一行。笔尖在纸上点了一个很重的句号,纸面上凹出一个小坑:

"方向可能不只有一条。"

写完把笔放下。笔在床垫的凹面上朝左滚了半圈,停住了。窗外虫叫断了两秒又续上了。风扇从左边摇头到右边,热风从左到右走了一遍。远处的暖气管在这个季节是空的——五月之后就停了暖,管里没有水也没有热,铁管壁上还留着冬天水垢的痕迹。管壁上的垢还在,但水流走了。

笔记合上。蓝色钢笔的刻字朝上——方向对了。四个字在风扇摇头带过来的风里看不太清,和笔杆上其他划痕混在一起。但指腹摸上去能摸出刻痕的凹槽。

方向对了。但路径不止一条。垢在管壁上。经验在时间里。程序算得出新管子里的水流速度,算不出十年后管壁上的垢有多厚。能走通的路不止一条。最短路径不是唯一路径——只是当前最优的那一条。