CodeCraft2016初赛的一点体会

作者: 虾米小华


华为CodeCraft软件精英挑战赛初赛于4月11日结束,我们的队伍有幸进入南京苏州赛区32强,顺利晋级将于5月中旬举行的复赛。

初赛持续了整整一个月。这一个月下来,还是很累的。比赛的竞争非常激烈(个人感觉,南京苏州赛区是全国八大赛区竞争最激烈的),基本上就一直在不停地想新的算法以及优化代码。

赛题描述起来还是很简单:给你一个图,求一条从起点到终点的最短路径,但是要经过一些指定的中间节点,并且不能重复访问任何一个节点。然而想要解决它可就没那么容易了——这是一个NP-hard问题,不可能有多项式时间算法。话说要是有多项式时间算法的话,也不会被拿来当赛题了。

在比赛的前期,官方网站上只放出了10个初级和中级测试用例,图的规模不是很大,必经点数量也不算很多。我们最初的算法就是暴力枚举+剪枝,基于Dijkstra算法,当然这个方法是无法解决稍大一点的用例的,因为枚举的复杂度是指数级的,剪枝也有不同的方法,有人剪得好效果就比较好(怎么说,感觉还是有人品的成分在里面)。然后我们开始尝试启发式算法,最先写的是A star算法,A star算法的难点在于对于不同的问题要设计合理的启发式函数,写完后经过一些优化,提交后,效果很好。在比赛的前中期,因为A star算法的使用,我们最好的成绩曾进过赛区前三名。

到了比赛的后期,高级测试用例放出来了。A star算法没能解出部分高级用例,所以,我们又开始设计新的启发式算法。我们实现了遗传算法,并且也几经优化,尝试了很多技巧。遗传算法的参数以及各种遗传算子都有可能极大地影响程序的性能。后来,我们使用了混合算法——将遗传算法和前面的枚举、A star结合起来用,对不同规模的测试用例跑不同的算法,终于解出所有用例,分数也上到了90+。然而排行榜上竞争异常激烈,虽然一直保持在32强,也解出了所有用例,但是我们的名次也基本上是一直在下降。因为不断有队伍解出更好的解。这个时候,基本上我们能想到的方法也都尝试过了,代码也很难再有显著的优化。如果没有更好的算法,进复赛恐怕还是不能保障。

我开始留意比赛论坛上的各种信息,发现很多队都开始采用开源库,通过整数规划来求解赛题。因为比赛规则中说明了初赛可以使用开源库,但是复赛不能使用。所以我们从一开始就没打算走这种路子,心里也鄙视这种做法(因为毕竟你只是调用别人的API,而不是自己一行一行写出来的)。用开源库也不是那么容易的,首先要能够把问题抽象成合理的数学模型,然后才能使用开源库,而且要阅读开源库的使用文档,熟悉了它的API才行。于是,清明节的三天假期,我就在实验室里研究开源库,基本了解了用法,写出了一些测试程序,后来在小伙伴的协作下完整地实现了整数规划模块。实验室里也有个队伍,他们清明节之前也解出了所有15个用例,上了90分,于是他们认为应该可以稳进复赛了,清明节三天就出去玩了,等他们回来后查看排行榜,发现名次掉了十几名,一下子就被挤出了32强……

“嘴上说不要,身体还是蛮诚实的”。虽然我们一开始鄙视用开源库,最后还是采用了。因为当大家都在使用开源库而且效果比你完全靠自己写代码好得多时,你就得考虑是不是要转型了。也许你稍微一迟疑,就被远远地甩在后面,并且再难赶上。后来在论坛里,许多参赛选手抱怨比赛不公平,抱怨真正靠自己能力手写算法的人被“没有能力的”靠开源库的人超越。我也想了这个问题,最后得出的结论是,开源库并不是万能的,会使用开源库的人,自己设计的算法大概也不会差到哪里去,而且使用开源库是初赛允许的,既然允许,那选手就有自由决定是否采用。你既然觉得不公平觉得那些使用了开源库的人都是“渣渣”,那么最好的办法是,你也使用开源库,然后打败那些同样使用了开源库的人

正如战场上允许使用剑和自动步枪,虽然少数用剑的人能够靠着自己精妙绝伦的剑法叱咤战场,但不幸的是大部分使用剑的人都被使用自动步枪的人秒杀,但是你不能因为自己剑法好就说那些使用枪的人都是渣渣。毕竟,能用剑挡住子弹的人永远是极少数。

最后我们的算法是整数规划+遗传算法。即使用开源库,也不是就所向披靡了。在同一个问题上,采用不同的开源库,以及建模方法的不同,都会导致性能也不尽相同。比如我们使用的开源库就无法解决最后两个测试用例,所以还是采用了混合算法。最后的分数定格在96,也顺利地闯进复赛。

从3月19日第一次到4月11日最后一次,我们累计提交了110次代码。以下是部分提交后的截图,分别对应“第一次提交” “第一次尝试A star算法” “第一次用枚举+Astar+遗传解出所有15个用例”以及“最终的版本——整数规划+遗传算法”:

第一次提交

第一次尝试A star算法

第一次用枚举+Astar+遗传解出所有15个用例

最终的版本——整数规划+遗传算法

至于感想,多少还是有些的。团队合作不必多说,就算一个团队里没有大神,不错的合作大抵也能起到“三个臭皮匠顶个诸葛亮”的效果;然后就是要不断尝试新的idea,不能因为一点小小的进步就自满得意,因为很多时候竞争对手的实力会超出你的想象;另外就是要广泛获取有价值的信息,关注比赛动态,看看别人都在尝试哪些路子,也许可以帮助自己获取新的idea,这样可以避免固步自封。最后就是足够的时间与付出了,初赛的一个月,虽然我们手头也都有自己的事,但是基本上每天都在提交代码,累计写的代码估计有3000+行。后来华为组织的复赛队伍的见面活动,还了解到有队伍代码写了5000+行的。当然不是说代码量越大越好(听说也有队伍用500行左右就上97分),但是代码量至少从一个方面反映了一个队伍在这项活动中付出的努力。粗略地了解过,基本上初赛进了top64的,大概累计代码量都不会少于1000行。

其实我们一开始并没有什么目标,就是觉得这个比赛看起来组织得不错(虽然后来竞赛规则遭到了很多选手的吐槽),奖励也比较可观,就抱着玩一玩的心态报了名。参加了比赛之后只想着能进复赛就好了,谁知竞争程度远比自己想象的还要激烈,每次只要一有“差不多能进复赛了吧”“这样还不进复赛!”的想法,往往过不了几天就要被打脸。那种眼睁睁地看着自己队伍名次一直往下掉,而自己却无能为力的感觉,真心不好受。但是又不忍放弃。比赛论坛上也不断地有选手喊“心累”,但大家依旧是在不停的写着代码,讨论着什么算法好,不论是大神还是菜鸟。努力不懈的程序员们永远都是那么可爱。

据说初赛的奖励除了纪念品和证书,还有“实习直通车”,好像可以不用面试就能去华为参加实习;以及校招免技术面,只有一个HR面,过了就能拿special offer。不过这些倒不是我最关心的,华为能否兑现承诺也无所谓了。好好珍惜最后两年多的学生生涯是我目前的想法,虽然听起来有点naive。

不过作为自己给自己的奖励,我买了人生第一副机械键盘——Cherry MX Board 2.0。

南研所的黑天鹅 上上周去华为南研所参加复赛36支队伍的见面活动,发现原来在群里给人感觉牛X得一塌糊涂的大神们一个个都是那么的腼腆羞涩,反倒是一些队里的妹子表现得比较大方。一顿免费的午餐之后,顺便和同去的同学们逛了南研所,风景挺好的,而且坐落于郊区比较安静,这张黑天鹅照片,就是在南研所拍摄的。只是南研所的氛围没有我想象得那么活跃,大概研究所都是这个样?

关于复赛,依旧没有什么特别的想法。只要到时候不要被大神们虐得太惨,就心满意足了。

-----EOF-----

Categories: 随笔 Tags: 算法 寻路 华为 CodeCraft