在 200 多年前,南非有一个地理学家在绘制地图的时候发现,所有的地图可以只使用四种不同的颜色来标记各个区域,同时可以保证相邻的两个区域不会被标记为一样的颜色,于是他把这一猜想命名为地图填色问题。这一猜想首先被数学家们注意到,可是全世界的数学家苦思冥想了 100 多年,也没能够证明它是否正确。直到 50 年前,一位计算机学家通过电脑穷举了所有的可能性,证明了地图填色猜想是正确的!一开始数学家们并不承认这种证明方法,但随着计算机的普及,越来越多的人认可了使用计算机辅助证明数学问题的方式。
有人可能会问,我们为什么要花这么大的精力去研究一个给地图填颜色的问题呢?因为在实际生活中,有很多问题都可以转化为更为广义的图着色问题。例如在学校安排课表时,需要为每个课程分配教室资源,为了避免把两节同一时间段的课程分配到相同的教室中,我们可以把它转化为图着色问题。把每个课程看作拓扑图的一个顶点,为同一时间段的两个课程之间添加冲突边,然后把教室看作可用颜色,那么解决这样一个图着色问题就同时解决了排课表的问题,类似的火车、飞机的调度排班也通过图着色问题来解决。
要解决图着色问题,最简单的方法就是暴力枚举出所有的方案并找到可行解,但当拓扑图的规模变大时,搜索方案的组合数会呈指数规模增长。实际生活中遇到的排班调度问题可能是很大规模的图着色问题,这时就需要一些启发式算法了。
让我们先看一种很经典的局部搜索式启发式算法。首先我们为拓扑图中每个顶点随机分配颜色,这样可以得到一个初始解,由于是随机分配的颜色,初始解中可能存在冲突边,所以初始解不一定是可行解。但是我们可以通过一步步的优化来尝试将初始解变成一个可行解,具体的做法是:“在当前解中寻找一个顶点,为它分配一个新的颜色,并且在分配新的颜色之后,整个解中冲突边的数目会有所下降。” 理想情况下,在一定次数的迭代之后,当前解中冲突边的数目将会降为 0,那么这时也就找到了一个可行解。让我们看一个简单的动画演示:

单击图片播放动画
可以看到,初始解里面有 6 条冲突边,经过两次迭代之后,冲突边的数目就降为了 2 条,可之后就陷入了僵局,冲突边的数目一直没法再下降。仔细观察可以发现,算法陷入了一个死循环,在反复调整节点 1 上的颜色,这就是启发式算法中很常见的陷入局部最优解的现象。

为了让算法能够跳出局部最优解,我们可以为算法赋予一定的随机性,也就是在搜索的过程中给予一定的扰动。但是如何控制扰动的度呢?如果扰动太小就起不到什么作用,但扰动太大就成了随机算法。我们知道在生活中,给一块金属加热后,它内部中粒子的随机运动就会变多,而当金属随着时间渐渐冷却时,它内部中的粒子活动就会逐渐变少,最后趋于稳定。那么我们也可以一开始给算法较多的扰动,之后随着迭代步数的增加,逐渐减少扰动。这样算法就有机会探索更多的解空间,而不容易陷入局部最优解,这就是模拟退火算法的思路。下面的动画演示了模拟退火算法跳出最局部最优解的过程:

单击图片播放动画
可以看到,随着扰动的加入,冲突边的数目可能会增加,但是算法也更不容易陷入局部最优的死循环,最终找到了一个可行解。
源码链接:https://github.com/zjl9959/algviz-launch/blob/main/graph/GraphColor.ipynb
开源动画引擎(欢迎 Star):https://github.com/zjl9959/algviz