我有一个包含81个顶点的漂亮图表(列表)(每个顶点都是Vertex类的一个实例)。每个顶点有20个邻居。每个顶点都有许多可能的值(范围从1到9),给定一些对问题的初始约束将平均为4或5.我在此图上实现了一个简单的DFS,它使节点具有较少的可能值, foreach值构建另一个只有一个可能值的“深度复制”图,最后再将“深度复制”图再次传递给DFS。问题在于速度; cProfiling我的代码我发现我的Mac用来解决这个问题的641秒中的635个由copy.deepcopy使用。这个问题有没有解决方法?这是我的DFS:
def dfs(graph):
global initial_time_counter
if all(len(i.possible_values)==1 for i in graph):
sys.exit("Done in: %s" % (time.time()-initial_time_counter))
#find out the non-solved vertex with minimum possible values
min_vertex=sorted(filter(lambda x: len(x.possible_values)>1,graph),
key=lambda x: len(x.possible_values))[0]
for value in min_vertex.possible_values:
sorted_graph_copy=sorted(copy.deepcopy(graph), key=lambda x: len(x.possible_values))
min_vertex_copy=filter(lambda x: len(x.possible_values)>1,sorted_graph_copy)[0]
sorted_graph_copy.remove(min_vertex_copy)
if min_vertex_copy.try_value(value): #Can this vertex accept value -> value?
min_vertex_copy.set_value(value) #Yes, set it.
sorted_graph_copy.append(min_vertex_copy) #Append it to the graph.
dfs(sorted_graph_copy) #Run the DFS again.
return False
P.S。你最聪明的人可能已经明白这个问题通常被称为数独。请注意,我不是在寻找特定于数独的答案,而是以抽象的方式分析问题。
[编辑]
同样的问题,用顶点的纯字符串表示逼近,采用< 0.75秒待解决。如果有人在将来遇到类似的问题,我会发布整个代码供参考:
import sys,time
def srange():
return [[x,y] for x in range(9) for y in range(9)]
def represent_sudoku(sudoku):
print "\n".join(["|".join([str(elem) for elem in line]) for line in sudoku])
#Hard sudoku
sudoku=[[4, 0, 0, 0, 0, 0, 8, 0, 5], [0, 3, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 7, 0, 0, 0, 0, 0], [0, 2, 0, 0, 0, 0, 0, 6, 0], [0, 0, 0, 0, 8, 0, 4, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 6, 0, 3, 0, 7, 0], [5, 0, 0, 2, 0, 0, 0, 0, 0], [1, 0, 4, 0, 0, 0, 0, 0, 0]]
represent_sudoku(sudoku)
def get_nbs(x,y,sudoku,also_incomplete=False):
line_nbs=sum([elem for elem in sudoku[y] if ((elem!=[0] and len(elem)==1) or also_incomplete)],[])
column_nbs=sum([sudoku[xline][x] for xline in range(9) if ((sudoku[xline][x]!=[0] and len(sudoku[xline][x])==1) or also_incomplete)],[])
area_nbs=[[j for j in i[(x/3)*3:(x/3)*3+3] if ((j!=[0] and len(j)==1) or also_incomplete)] for i in sudoku[(y/3)*3:(y/3)*3+3]]
area_nbs=sum(sum(area_nbs,[]),[])
if not also_incomplete:
return list(set(line_nbs+column_nbs+area_nbs))
return line_nbs+column_nbs+area_nbs
for x,y in srange():
sudoku[y][x]=[sudoku[y][x]]
def base_cleanup(sudoku):
while 1:
something_changed=False
for x,y in srange():
if sudoku[y][x]==[0] or len(sudoku[y][x])>1:
possible_values=range(1,10) if sudoku[y][x]==[0] else sudoku[y][x]
sudoku[y][x]=list(set(possible_values)-set(get_nbs(x,y,sudoku)))
if sudoku[y][x]==[]:
return False
something_changed=True if possible_values!=sudoku[y][x] else False
else:
sudoku[y][x]=sudoku[y][x]
if not something_changed:
break
return sudoku
def dfs(graph):
global s
if graph==False:
return False
if all(sum([[len(elem)==1 for elem in line] for line in graph],[])):
represent_sudoku(graph)
sys.exit("Done in: %s" % (time.time()-s))
enumerated_filtered_sudoku=filter(lambda x: len(x[1])>1, enumerate(sum(graph,[])))
sorted_enumerated_sudoku=sorted(enumerated_filtered_sudoku,key=lambda x: len(x[1]))
min_vertex=sorted_enumerated_sudoku[0]
possible_values=[value for value in min_vertex[1]]
for value in possible_values:
graph_copy=[[elem for elem in line] for line in graph]
y,x=elements_position[min_vertex[0]]
if not any(value==i for i in get_nbs(x,y,graph_copy)):
graph_copy[y][x]=[value]
if base_cleanup(graph_copy)!=False:
graph_copy=base_cleanup(graph_copy)
if graph_copy:
dfs(graph_copy)
return False
sudoku = base_cleanup(sudoku)
elements_position = {i:srange()[i] for i in range(81)}
s = time.time()
dfs(sudoku)
答案 0 :(得分:3)
cPickle比深度镜检更快:
15 @profile
16 def test():
17 100 967139 9671.4 95.0 b = deepcopy(a)
18 #c = copy(a)
19 #d = ujson.loads(ujson.dumps(a))
20 #e = json.loads(json.dumps(a))
21 #f = pickle.loads(pickle.dumps(a, -1))
22 100 50422 504.2 5.0 g = cPickle.loads(cPickle.dumps(a, -1))
答案 1 :(得分:2)
Deepcopy可能很多比简单地复制相同数量的数据慢,可能是因为检测循环的所有努力。如果您自己以避免循环的方式复制图形(简单,因为您知道网络拓扑)而不是委托给深度复制,它可能会给您一个严重的加速。我有一个50%的加速速度来复制一个非常简单的数据结构元素(使用理解),如果复杂的节省了更多的节省,我不会感到惊讶。
当然,如果您可以避免在每一步完整地复制整个州,那么可以获得更大的加速。例如,由于您首先搜索深度,因此您可以切换维护撤消堆栈的方法:只记录您选择的每个选项列表,并在回溯时恢复它们。
答案 2 :(得分:1)
你需要复制整个图表吗?通常,您只需要在搜索的任何步骤中修改其中的一小部分,因此使用不可变数据结构并仅重建所需内容可能更有效。这不适用于循环,但我怀疑你的图表是一个列表?
我在clojure中解决了类似的问题,它本身支持不可变结构,并设法以这种方式获得合理的效率。但我不知道python的任何不可变的数据结构库(在某处有一个写入时复制列表包 - 这就足够了吗?)
[只是为了澄清一个简单的例子 - 你是对的,元组是不可变的,所以如果你有一个由这样的元组构成的树:(1, (2, 3), (4, (5, 6)))
那么你可以生成一个像这样的新树{{1通过仅创建两个元组 - 您可以复制(1, (2, 99), (4, (5, 6)))
而无需执行深层复制。对于python来说,什么是clojure,以及我不知道的是更复杂的结构(如哈希表)遵循相同的原则。它使得dfs非常简单,因为你根本不必担心“上游”更改值。]
答案 3 :(得分:0)
我并不完全了解数独的细节,但如果您使用图表的单个副本并为每个节点提供包含以下内容的类,该怎么办?
1)相邻顶点列表 2)“访问”标志,可用于跟踪已经看到的内容和未看到的内容
您的深度优先搜索仍然可以是递归的,但您不是将图表的修改后的子集打包,而是将节点标记为“已访问”并继续,直到无处可去。如果您的图表已连接,它似乎应该可以正常工作......