Algorithm 一种高效的平面四色图评分着色算法

Algorithm 一种高效的平面四色图评分着色算法,algorithm,graph,Algorithm,Graph,我正在做一个游戏,你必须用四色定理给图像上色。相邻区域不能具有相同的颜色。共有四种颜色,红、绿、蓝、黄,每个红色区域得10分,绿色6分,蓝色3分,黄色1分 我想要一个算法能够计算出任何给定图像的最大分数。我有从图像中提取平面图的代码,它为每个区域提供了一个相邻区域的列表 到目前为止,我已经完成了一个蛮力实现,它检查了所有可能的颜色,但是对于n个区域,它的增长速度像4**n。我可以采取的一种方法是尽可能地优化搜索 有没有更快的办法?我知道对于2种颜色有一个线性时间算法,但是出于游戏设计的原因,我通

我正在做一个游戏,你必须用四色定理给图像上色。相邻区域不能具有相同的颜色。共有四种颜色,红、绿、蓝、黄,每个红色区域得10分,绿色6分,蓝色3分,黄色1分

我想要一个算法能够计算出任何给定图像的最大分数。我有从图像中提取平面图的代码,它为每个区域提供了一个相邻区域的列表

到目前为止,我已经完成了一个蛮力实现,它检查了所有可能的颜色,但是对于n个区域,它的增长速度像4**n。我可以采取的一种方法是尽可能地优化搜索

有没有更快的办法?我知道对于2种颜色有一个线性时间算法,但是出于游戏设计的原因,我通常不会生成可以用2种颜色着色的图像

谢谢:)

编辑:这里的sascha请求是一些python dict示例,它们的键是区域id,列表是该区域的邻居列表

easy={2:[4],4:[2,3,14,13],3:[4],14:[4],13:[4]}

高分:46分(我想)

(my python bruteforce 0.1s)

中音={2:[4,5,6],4:[2,3],3:[4,18],5:[2,6],6:[5,2,13,18],13:[6,20,21,22],18:[6,3,20,22],20:[18,13],22:[18,13],21:[13]}

总分:77分

(my python bruteforce 7.2s)

hard={2:[5,6,9],5:[2,4],4:[5,23],6:[2,7,10],3:[8,16],8:[3,7,12],7:[6,8,10,11],9:[2,10],10:[6,9,7,13,14,15,17,18],11:[7,12,13],12:[8,11,15,16,19],13:[10,13,12,14,17,19],16:[3,12,25,27],17,10,15,18:[10,20,19][18,22,24,26,27,25],22:[20],23:[4,24,26],24:[23,20],25:[16,20],26:[23,20],27:[19,20,16]}

(我的python蛮力未知)


编辑2:

所以我完成了游戏,如果你感兴趣的话,你可以去看看

为了比赛,我意识到我只需要一个高分,而不是绝对的高分(这就是问题的目的)。因此,我实施了贪婪着色,并运行了10000次,每次洗牌图形并获得最佳评分结果。在所有小于30个区域的小型电路板上,这会产生与蛮力方法相同的结果,但它的时间复杂性会更好地扩展到大型电路板。因此,它可能无法找到绝对最佳的解决方案总是找到一个非常好的


非常感谢@SaiBot和@sascha的帮助:)

这是我对这个问题的尝试。我没有想出更好的时间复杂度,但优化了蛮力

我一个接一个地处理节点,只允许着色,这样就不会有两个相邻节点具有相同的颜色

def checkSolution(graph, coloring):
    validColoring=1
    for node in graph:
        for neighbour in graph[node]:
            if coloring[node] == coloring[neighbour]:
                print("wrong coloring found "+ str(node) + " and " + str(neighbour) + " have the same color")
                validColoring = 0
    if validColoring:
        print("Coloring is valid")
我为每个中间(非完全)着色添加了一个上界估计值。为此,我假设每个非着色节点都将使用得分最高的颜色着色(只允许使用与已着色的相邻节点不同的颜色)。因此,在上限计算中,两个尚未着色的相邻节点都可以被着色为“红色”。通过此估计,我构建了一个分支定界算法,当上限估计值仍然低于当前最大值时,该算法终止当前搜索路径

小型图形的运行时间小于1 ms,中型图形的运行时间小于15 ms,大型图形的运行时间小于3.2秒。三个图形的结果分别为46、77和194

import time
import copy

def upperBoundScore(graph, dfsGraphOder, dfsIndex, coloring, scoring, currentScore):
    maxAdditionalScore = 0;
    for i in range(dfsIndex, len(dfsGraphOder)):
        neighbourColors = {coloring[node] for node in graph[dfsGraphOder[i]]}
        possibleColors = {1, 2, 3, 4} - neighbourColors
        if len(possibleColors) < 1:  # if for one node no color is available stop
            return -1
        maxAdditionalScore += scoring[list(possibleColors)[0]]
    return currentScore+maxAdditionalScore

def colorRemainingGraph(graph, dfsGraphOder, dfsIndex, coloring, scoring, currentScore):
    global maxScore
    global bestColoring
    # whole graph colored
    if dfsIndex == len(dfsGraphOder):
        if currentScore > maxScore:
            maxScore = currentScore
            bestColoring = copy.deepcopy(coloring)
    # only proceed if current coloring can get better then best coloring
    elif upperBoundScore(graph, dfsGraphOder, dfsIndex, coloring, scoring, currentScore) > maxScore:
        neighbourColors ={coloring[node] for node in graph[dfsGraphOder[dfsIndex]]}
        possibleColors = list({1, 2, 3, 4} - neighbourColors)
        for c in possibleColors:
            coloring[dfsGraphOder[dfsIndex]] = c
            currentScore += scoring[c]
            colorRemainingGraph(graph, dfsGraphOder, dfsIndex+1, coloring, scoring, currentScore)
            currentScore -= scoring[c]
            coloring[dfsGraphOder[dfsIndex]] = 0

#graph = {2: [4], 4: [2, 3, 14, 13], 3: [4], 14: [4], 13: [4]}
#graph = {2: [4, 5, 6], 4: [2, 3], 3: [4, 18], 5: [2, 6], 6: [5, 2, 13, 18], 13: [6, 20, 21, 22], 18: [6, 3, 20, 22], 20: [18, 13], 22: [18, 13], 21: [13]}
graph = {2: [5, 6, 9], 5: [2, 4], 4: [5, 23], 6: [2, 7, 10], 3: [8, 16], 8: [3, 7, 12], 7: [6, 8, 10, 11], 9: [2, 10], 10: [6, 9, 7, 13, 14, 15, 17, 18], 11: [7, 12, 13], 12: [8, 11, 15, 16, 19], 13: [10, 11, 15], 14: [10, 15], 15: [10, 13, 12, 14, 17, 19], 16: [3, 12, 25, 27], 17: [10, 15, 18], 18: [10, 17, 19, 20], 19: [15, 18, 12, 27], 20: [18, 22, 24, 26, 27, 25], 22: [20], 23: [4, 24, 26], 24: [23, 20], 25: [16, 20], 26: [23, 20], 27: [19, 20, 16]}

# 0 = uncolored, 1 = red, 2 = green, 3 = blue, 4 = Yellow
scoring = {1:10, 2:6, 3:3, 4:1}
coloring = {node: 0 for node in graph.keys()}
nodeOrder = list(graph.keys())
maxScore = 0
bestColoring = {}

start = time.time()
colorRemainingGraph(graph, nodeOrder, 0, coloring, scoring, 0)
end = time.time()
print("Runtime: "+ str(end - start))
print("Max Score: "+str(maxScore))
print(bestColoring)

下面是一些使用python的简化整数编程方法

其基本思想是使用现代高质量混合整数规划软件的惊人功能,而不需要自己实现算法。我们只需要定义模型(也许还需要调整一些东西)

请记住,(混合)整数规划通常是NP难的,我们假设这些启发式算法对我们的问题有效

代码可能看起来有点难看,因为使用的建模工具非常低级。模型本身的结构非常简单

代码(python3;numpy、scipy、cylp+CoinOR Cbc解算器) 这里是类似于原型的代码,缺少最终解决方案的提取。因为这只是一个演示(您没有标记语言),这表明它是一种可行的方法

从cyp.cy导入CyClpSimplex
进口itertools
将numpy作为np导入
将scipy.sparse导入为sp
从timeit导入默认的\u计时器作为时间
“实例”
#hard={2:[4],4:[2,3,14,13],3:[4],14:[4],13:[4]}
#hard={2:[4,5,6],4:[2,3],3:[4,18],5:[2,6],6:[5,2,13,18],13:[6,20,21,22],18:[6,3,20,22],20:[18,13],22:[18,13],21:[13]}
hard={2:[5,6,9],
5: [2, 4],
4: [5, 23],
6: [2, 7, 10],
3: [8, 16],
8: [3, 7, 12],
7: [6, 8, 10, 11],
9: [2, 10],
10: [6, 9, 7, 13, 14, 15, 17, 18],
11: [7, 12, 13],
12: [8, 11, 15, 16, 19],
13: [10, 11, 15],
14: [10, 15],
15: [10, 13, 12, 14, 17, 19],
16: [3, 12, 25, 27],
17: [10, 15, 18],
18: [10, 17, 19, 20],
19: [15, 18, 12, 27],
20: [18, 22, 24, 26, 27, 25],
22: [20],
23: [4, 24, 26],
24: [23, 20],
25: [16, 20],
26: [23, 20],
27: [19, 20, 16]}
“”“预处理->邻居冲突”
(排序对称后删除重复项)
备注:对于困难的用例,可以尝试考虑特殊的
图形的特征,如(不一定适用于此问题)
弦->计算P(多项式时间)=>相当好的凸包中的所有最大团
这里:只需禁止冲突对(在每个颜色维度中)。
"""
开始时间=时间()
冲突=[]
对于键,硬.items()中的VAL:
对于val中的val:
Welcome to the CBC MILP Solver 
Version: 2.9.9 
Build Date: Jan 15 2018 

command line - ICbcModel -solve -quit (default strategy 1)
Continuous objective value is -200 - 0.00 seconds
Cgl0003I 0 fixed, 0 tightened bounds, 20 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 24 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 16 strengthened rows, 0 substitutions
Cgl0004I processed model has 153 rows, 100 columns (100 integer (100 of which binary)) and 380 elements
Cutoff increment increased from 1e-05 to 0.9999
Cbc0038I Initial state - 0 integers unsatisfied sum - 0
Cbc0038I Solution found of -194
Cbc0038I Before mini branch and bound, 100 integers at bound fixed and 0 continuous
Cbc0038I Mini branch and bound did not improve solution (0.01 seconds)
Cbc0038I After 0.01 seconds - Feasibility pump exiting with objective of -194 - took 0.00 seconds
Cbc0012I Integer solution of -194 found by feasibility pump after 0 iterations and 0 nodes (0.01 seconds)
Cbc0001I Search completed - best objective -194, took 0 iterations and 0 nodes (0.01 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from -194 to -194
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                -194.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.01
Time (Wallclock seconds):       0.01

Total time (CPU seconds):       0.01   (Wallclock seconds):       0.01

  CoinOR CBC used 0.013 secs
  Complete process used 0.042 secs