我正在制作一个游戏,你必须使用4色定理为图像着色。没有相邻区域可以是相同的颜色。有四种颜色,红绿蓝黄色,每个红色区域有10个点,绿色有6个,蓝色有3个,黄色有1个。
我希望算法能够计算出任何给定图像的最大分数。我有从图像中提取平面图的代码,它为每个区域提供了它的邻居列表。
到目前为止,我已经完成了一项强力实施,它会检查所有可能的颜色,但是对于n个区域,这种颜色会增加到4 ** n。我可以采取的一种方法是尽可能地尝试优化此搜索。
有没有更快的方法?我知道2种颜色有线性时间算法但是出于游戏设计的原因,我通常不会生成可以用2种颜色着色的图像。
谢谢:)
编辑:因为这里的sascha请求是一些示例python dicts,它们的键是区域id,列表是该区域的邻居列表
easy = {2:[4],4:[2,3,14,13],3:[4],14:[4],13:[4]}
最高分:46(我认为)
(我的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
(我的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,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]}
(我的python bruteforce未知)
EDIT2:
所以我完成了游戏,如果你有兴趣,可以查看here.
为了游戏,我意识到我只需要一个高分而不是绝对的最高分(这就是问题所要求的)。因此,我实施了贪婪的着色,并且每次运行10,000次,并且获得最佳得分结果。在少于30个区域的所有小板上,这产生与蛮力方法相同的结果,但是时间复杂度对于更大的板更好地扩展。所以它可能找不到绝对最好的解决方案,总能找到一个非常好的解决方案。
非常感谢@SaiBot和@sascha的帮助:)
答案 0 :(得分:2)
这是我对这个问题的尝试。我无法提出更好的时间复杂性,但优化了蛮力。
我逐个处理节点,只允许着色,使得没有两个邻居节点具有相同的颜色。
我为每个中间(非完整)着色添加了上限估计。为此我假设每个非彩色节点将以最高得分的颜色着色(仅允许与已着色的邻居不同的颜色)。因此,在上限计算中,尚未着色的两个相邻节点都可以着色"红色"。通过这种估计,我构建了一个分支定界算法,当上限估计值仍低于当前最大值时终止当前搜索路径。
小图表的运行时间小于 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)
对于大图,得到的颜色为(1 =红色,2 =绿色,3 =蓝色,4 =黄色):
{2:1,3:1,4:1,5:2,6:2,7:1,8:2,9:2,10:3,11:2,12:1,13: 1,14:1,15:4,16:2,17:2,18:1,19:2,20:2,22:1,23:2,24:1,25:1,26:1, 27:1}
要验证算法输出的着色是否正确,可以使用以下代码,该代码检查任何两个邻居节点是否具有相同的颜色。
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 :(得分:2)
这里有一些使用python的简化整数编程方法。
基本思想是使用现代高质量混合整数编程软件的惊人功能,而无需自己实现算法。我们只需要定义模型(也许可以调整一些东西)!
请记住,(混合)整数编程通常是NP难的,我们假设那些启发式算法在这里解决了我们的问题!
代码可能看起来有些难看,因为使用的建模工具非常低级。模型本身的结构非常简单。
这里是类似原型的代码,它缺少最终解决方案的提取。由于这只是一个演示(你没有标记语言),这只是表明它是一种可行的方法。
from cylp.cy import CyClpSimplex
import itertools
import numpy as np
import scipy.sparse as sp
from timeit import default_timer as time
""" Instances """
# 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]}
""" Preprocessing -> neighbor conflicts
(remove dupes after sorting <-> symmetry
Remark: for difficult use-cases one could try to think about special
characteristics of the graph, like (not necessarily for this problem)
chordal -> calc all max-cliques in P(olynomial-time) => pretty good convex-hull
Here: just forbid conflicting-pairs (in each color-dimension).
"""
START_T = time()
conflicts = []
for key, vals in hard.items():
for val in vals:
conflicts.append((key, val))
conflicts_np = np.array(conflicts)
conflicts_np = np.sort(conflicts, axis=1)
conflicts_np = np.unique(conflicts_np, axis=0)
""" Preprocessing -> map IDs to gapless range [0-N)
"""
unique = np.unique(conflicts)
old2new = {}
new2old = {}
counter = itertools.count()
N = unique.shape[0]
for i in unique:
new_id = next(counter)
old2new[i] = new_id
new2old[new_id] = i
conflicts_np = np.vectorize(old2new.get)(conflicts_np)
""" Sparse conflict matrix """
conflict_matrix = sp.coo_matrix((np.ones(conflicts_np.shape[0]*2),
(np.tile(np.arange(conflicts_np.shape[0]), 2),
conflicts_np.ravel(order='F'))), shape=(conflicts_np.shape[0], N*4))
I, J, V = sp.find(conflict_matrix)
""" Integer Programming """
model = CyClpSimplex()
# 4 colors -> 4 binary vars per element in N
x = model.addVariable('x', N*4, isInt=True)
# scoring: linear-objective
model.objective = -np.hstack((np.full(N, 10), np.full(N, 6), np.full(N, 3), np.full(N, 1)))
# sub-opt way of forcing binary-constraints (from ints)
# (this awkward usage is due to problem with cylp in the past)
model += sp.eye(N*4) * x >= np.zeros(N*4)
model += sp.eye(N*4) * x <= np.ones(N*4)
# conflicts in each color-dimensions
# sub-opt numpy/scipy usage
for ind, i in enumerate(range(4)):
if ind == 0:
model += conflict_matrix * x <= 1
else:
shifted_conflicts = sp.coo_matrix((V,(I,J+(ind*N))), shape=(conflict_matrix.shape[0], N*4))
model += shifted_conflicts * x <= 1
# force exactly one color per element
# sub-opt numpy/scipy usage
template = np.zeros(N*4)
template[0] = 1
template[N] = 1
template[2*N] = 1
template[3*N] = 1
all_color_dims = [sp.csc_matrix(np.roll(template, i).reshape(1,-1)) for i in range(N)]
model += sp.vstack(all_color_dims) *x == 1
cbcModel = model.getCbcModel() # Clp -> Cbc model / LP -> MIP
start_time = time()
status = cbcModel.solve()
end_time = time()
print(" CoinOR CBC used {:.{prec}f} secs".format(end_time - start_time, prec=3))
print(" Complete process used {:.{prec}f} secs".format(end_time - START_T, prec=3))
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
你的&#34;大&#34; 实例在 0.042秒(子选择代码的完整时间)和 0.013 内解决secs用在核心求解器中。当然这仅仅是一个例子,解释这不是那么科学!
结果与 SaiBot 有趣的定制解决方案(以及较小的示例)相同!
(一些早期的代码有一个得分错误,这让我要求Saibot仔细检查他的解决方案,我现在可以重现!)
MIP解算器应该可用于大多数架构和环境,甚至可能在移动设备上(具有一些潜在的非平凡构建过程)。这些建模/使用取决于建模系统和周围软件。