在数学界有一个传说,那就是不管多么复杂的地图,只需要使用至多四种颜色,就可以填充地图,且满足相邻的两个区域的颜色不同。当然,这里的相邻是指至少有一条边相邻,只是点相邻就不算相邻。
那么,现在就出现一个问题,如果给了你一个地图,并且标记了这个地图的不同区域,你要怎么找到一种填色方案,且至多只能使用四种颜色呢?
为了方便表述问题,我们使用一个二维数组来表示一个地图,并用不同的数字表示不同的区域,用 -1 表示这个位置不在地图上。比如如下地图(灰色表示非地图区域,比如湖泊之类的):
这个地图,一共有 10 块区域,我们需要用红黄蓝绿至多四种颜色,来给这 10 块区域上色,但是,又要求相邻的区域不能用重复的颜色。
首先,我们用代码来表示这个地图,如下:
MAP = [
[ 1, 1, 1, 1, 2, 2, -1],
[ 3, 3, 2, 2, 2, -1, -1],
[ 4, 3, 3, 5, 6, 6, 6],
[ 4, 5, 5, 5, 6, 7, 6],
[ 4, 5, 8, 7, 7, 7, 9],
[ 4, 4, 8, -1, 9, 9, 9],
[ 8, 8, 8, 10, 10, 10, 9],
[-1, -1, -1, -1, 10, 10, -1]
]
然后,我们定义一个填色数组,来使用 print
函数来打印出颜色。
COLORS = ['\033[1;30;41m', '\033[1;30;42m', '\033[1;30;43m', '\033[1;30;44m']
接着,我们定义一个字典,来表示不同的区域应该填什么颜色。当然,这个字典现在是空的。
COLOR_MAP = {}
当然,一开始我们没有填色,所以,这个函数跑出来的效果如下:
接下来,我们可以开始填色了,填色的算法其实很简单,就是不断地尝试所有可能的方案,只要遇到一个可以填完颜色的方案为止。具体步骤如下:
- 先计算每个区域相邻的区域有哪些,构建一个邻接区域表。
- 递归调用以下过程,直到所有区域都被填上颜色了,算法结束:
- 检查当前区域所有可以使用的颜色(即,查看自己邻接的区域都用了哪些颜色,没被使用过的就是自己可以用的)。
- 如果当前区域没有颜色可以使用,则返回失败给上一层,上一层则会尝试换一种颜色。
- 从当前区域可使用的颜色中,逐个尝试,如果下一层一直迭代到最后填色成功,则整体填色成功,否则,就换个颜色继续尝试。
执行完以上步骤之后,地图填色就成功了。这里贴出参考代码:
def color_map(map):
def get_val(x, y):
if x < 0 or x >= width or y < 0 or y >= height:
return -1
return map[x][y]
def add_neighbor(x, y, neighbors):
val = map[x][y]
if val == -1:
return
if val not in neighbors:
neighbors[val] = set()
neighbors[val].add(get_val(x, y + 1))
neighbors[val].add(get_val(x, y - 1))
neighbors[val].add(get_val(x - 1, y))
neighbors[val].add(get_val(x + 1, y))
# 计算每个区域的邻边区域
width = len(map)
height = len(map[0])
neighbors = {}
for x in range(0, width):
for y in range(0, height):
add_neighbor(x, y, neighbors)
# 给第一个区域填上第一种颜色
color_map = {}
for i in range(0, len(neighbors)):
color_map[i + 1] = 0
color_map[1] = 1
# 从第二个区域开始,尝试所有不同的颜色
def coloring(level):
# 所有区域都填上颜色了,结束
if level > len(neighbors):
return True
# 获取当前区域的邻区域都用了什么颜色
neighbor = neighbors[level]
colors = set()
for n in neighbor:
if n == -1:
continue
color = color_map[n]
if color > 0:
colors.add(color)
# 如果没有颜色可以用了,则返回上层尝试别的颜色
if len(colors) == 4:
return False
# 对所有还可以使用的颜色进行尝试
for i in range(1, 5):
if i in colors:
continue
color_map[level] = i
if coloring(level + 1):
return True
# 换种颜色继续试
color_map[level] = 0
# 全都不行(理论上不可能),则返回 False
return False
# 开始着色
coloring(2)
return color_map
最后,执行代码:
if __name__ == '__main__':
COLOR_MAP = color_map(MAP)
print_map(MAP)
可以看到,程序最终输出的效果如下:
从效果中我们可以看到,地图被正确地填上了颜色,并且,相邻的区域用的都是不同的颜色。