2

81個​​の頂点を含む素敵なグラフ(リスト)があります(各頂点はVertexクラスのインスタンスです)。各頂点には20個の隣接があります。各頂点にはいくつかの可能な値(1から9の範囲)があり、問題に対するいくつかの初期制約を考えると、平均して4または5になります。このグラフに単純なDFSを実装しました。これにより、可能な値が少ないノードが取得されます。 foreach値は、可能な値の1つだけを持つ別の「deepcopied」グラフを作成し、最後に「deepcopied」グラフを再びDFSに再帰的に渡します。問題は速度です。cコードのプロファイリング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

PSは、この問題を理解しているかもしれませんが、通常は数独と呼ばれます。私は数独に固有の答えを探しているのではないことに注意してください。抽象的な方法で問題を分析してください。

[編集]

頂点の純粋な文字列表現でアプローチされた同じ問題は、解決するのに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)
4

4 に答える 4

3

cPickleはdeepcopyよりも高速です。

行数ヒットあたりのヒット時間%タイムラインの内容

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))
于 2015-04-01T08:11:45.350 に答える
2

ディープコピーは、おそらくループの検出にかかるすべての労力のために、単に同じ量のデータをコピーするよりもはるかに遅くなる可能性があります。ディープコピーに委任するのではなく、ループを回避する方法でグラフを自分でコピーすると(ネットワークトポロジを知っているので簡単です)、大幅なスピードアップが得られる可能性があります。非常に単純なデータ構造を要素ごとに(内包表記を使用して)コピーすることで50%のスピードアップが得られました。複雑なデータ構造でさらに大きな節約ができても、驚くことではありません。

もちろん、各ステップで状態全体の完全なコピーを作成することを回避できれば、より大きなスピードアップが得られます。たとえば、最初に深さを検索しているので、元に戻るスタックを維持するためのアプローチを切り替えることができます。選択した選択肢の各リストを記録し、バックトラック時にそれらを復元するだけです。

于 2012-04-12T17:58:02.580 に答える
1

グラフ全体をコピーする必要がありますか?通常、検索の任意のステップで変更するのはごく一部であるため、不変のデータ構造を使用し、必要なものだけを再構築する方が効率的です。これはループでは機能しませんが、グラフはリストだと思いますか?

私は、不変の構造をネイティブにサポートしているclojureで同様の問題を解決し、この方法で妥当な効率を得ることができました。しかし、Python用の不変のデータ構造ライブラリを知りません(どこかにコピーオンライトリストパッケージがあります-それで十分でしょうか?)

[簡単な例で明確にするために-タプルは不変であるということは正しいので、このようなタプルで作られたツリーがある場合:2つのタプルを作成するだけで(1, (2, 3), (4, (5, 6)))、このような新しいツリーを生成できます-なしでコピーできますディープコピーを実行します。clojureが持っているものと、Pythonについて私が知らないものは、同じ原則に従うはるかに複雑な構造(ハッシュテーブルなど)です。「アップストリーム」で値を変更することをまったく心配する必要がないため、dfsが非常に簡単になります。](1, (2, 99), (4, (5, 6)))(4, (5, 6))

psノーヴィグのアプローチの優雅さを理解したのは、あなたがしていることを実行し、それに伴うコストを確認することによってのみでした...

于 2012-04-13T12:15:14.223 に答える
0

私は数独の詳細を完全には認識していませんが、グラフの単一のコピーを使用して、各ノードに以下を含むクラスを与えるとどうなりますか?

1)隣接する頂点のリスト2)すでに表示されているものと表示されていないものを追跡するために使用できる「訪問済み」フラグ

深さ優先探索は引き続き再帰的に実行できますが、グラフの変更されたサブセットをパッケージ化する代わりに、ノードに「訪問済み」のマークを付けて、他に行く場所がなくなるまで続行します。グラフが接続されている場合は、機能するはずです...

于 2012-04-12T18:19:58.640 に答える