список всех путей от источника до стока в ориентированном ациклическом графе


person Tyler    schedule 19.07.2010    source источник
comment
Гарантируется ли вам, что существует только один источник, к которому в конечном итоге приводят все изменения узла, и есть только один приемник, к которому в конечном итоге приводят все цепочки узлов? Или источник и приемник являются частью более крупного графа, который простирается на объекты, ведущие от источника или в приемник?   -  person Omnifarious    schedule 19.07.2010
comment
Хм. Я точно не знаю, что вы имеете в виду под сменой узла, но есть конечное X различных источников и очень конечное Y различных приемников.   -  person Tyler    schedule 19.07.2010
comment
@Tyler - А цели дан отдельный источник, найти все пути к отдельному стоку?   -  person Omnifarious    schedule 19.07.2010
comment
@ Великолепно Да. Вот и все. :)   -  person Tyler    schedule 19.07.2010
comment
@Tyler - я дал алгоритм, который должен работать, хотя я его не тестировал. Я думаю, это более правильно, чем у Алекса, и есть довольно простая оптимизация, которая может быть произведена.   -  person Omnifarious    schedule 19.07.2010
comment
@Tyler - Я опубликовал версию, которую я действительно тестировал.   -  person Omnifarious    schedule 02.08.2010


Ответы (3)


Это основано на ответе Алекса Мартелли, но он должен работать. Это зависит от выражения source_node.children, дающего итерацию, которая будет перебирать все дочерние элементы source_node. Он также полагается на то, что у оператора == есть рабочий способ сравнения двух узлов, чтобы убедиться, что они одинаковы. Использование is может быть лучшим выбором. По-видимому, в используемой вами библиотеке синтаксис для получения итерации по всем дочерним элементам равен graph[source_node], поэтому вам нужно будет соответствующим образом скорректировать код.

def allpaths(source_node, sink_node):
    if source_node == sink_node: # Handle trivial case
        return frozenset([(source_node,)])
    else:
        result = set()
        for new_source in source_node.children:
            paths = allpaths(new_source, sink_node, memo_dict)
            for path in paths:
                path = (source_node,) + path
                result.add(path)
        result = frozenset(result)
        return result

Меня больше всего беспокоит то, что при этом выполняется поиск в глубину, это приведет к потере усилий, когда есть несколько путей от источника к узлу, являющемуся внуком, правнуком и т. Д., Все источники, но не обязательно родительские приемники. Если бы он запомнил ответ для данного узла источника и приемника, можно было бы избежать дополнительных усилий.

Вот пример того, как это будет работать:

def allpaths(source_node, sink_node, memo_dict = None):
    if memo_dict is None:
        # putting {}, or any other mutable object
        # as the default argument is wrong 
        memo_dict = dict()

    if source_node == sink_node: # Don't memoize trivial case
        return frozenset([(source_node,)])
    else:
        pair = (source_node, sink_node)
        if pair in memo_dict: # Is answer memoized already?
            return memo_dict[pair]
        else:
            result = set()
            for new_source in source_node.children:
                paths = allpaths(new_source, sink_node, memo_dict)
                for path in paths:
                    path = (source_node,) + path
                    result.add(path)
            result = frozenset(result)
            # Memoize answer
            memo_dict[(source_node, sink_node)] = result
            return result

Это также позволяет вам сохранять словарь мемоизации между вызовами, поэтому, если вам нужно вычислить ответ для нескольких узлов источника и приемника, вы можете избежать больших дополнительных усилий.

person Omnifarious    schedule 19.07.2010
comment
Прохладный. Вы можете мне объяснить, как вы это сделали? - person Tyler; 19.07.2010
comment
@ Тайлер, да, я пытаюсь вспомнить, как все прошло. :-) - person Omnifarious; 19.07.2010
comment
Замечательно, это было бы действительно полезно. - person Tyler; 19.07.2010
comment
@Tyler - Я понял, что алгоритм, который я написал, имеет лишь мимолетное сходство с тем, что вы хотите. Я собираюсь немного подумать об этом, но пока что удаляю свой ответ. - person Omnifarious; 19.07.2010
comment
Я получаю TypeError: can only concatenate tuple (not "instance") to tuple с path = (source_node,) + path - person Tyler; 19.07.2010
comment
@Omnifarious вы использовали изменяемый аргумент по умолчанию в примере allpaths, что может испортить ситуацию, если вы вызовете функцию более одного раза (рекурсия не учитывается) в той же программе. Исправлено. - person Boris Gorelik; 30.08.2011

Это действительно работает с networkx, и это нерекурсивно, что может быть хорошо для больших графов.

def find_all_paths(graph, start, end):
    path  = []
    paths = []
    queue = [(start, end, path)]
    while queue:
        start, end, path = queue.pop()
        print 'PATH', path

        path = path + [start]
        if start == end:
            paths.append(path)
        for node in set(graph[start]).difference(path):
            queue.append((node, end, path))
    return paths
person bukzor    schedule 16.04.2011

Я не уверен, доступны ли специальные оптимизации - прежде чем искать какую-либо из них, я бы сделал простое рекурсивное решение, что-то вроде (использование networkx только той функции, что индексирование графа узлом дает итерацию, дающую его соседние узлы [[dict, в случае networkx, но я не использую его в частности]]) ...:

def allpaths(G, source_nodes, set_of_sink_nodes, path_prefix=()):
  set_of_result_paths = set()
  for n in source_nodes:
    next_from_n = []
    for an in G[n]:
      if an in set_of_sink_nodes:
        set_of_result_paths.add(path_prefix + (n, an))
      else:
        next_from_n.append(an)
    if next_from_n:
      set_of_result_paths.update(
          allpaths(G, next_from_n, set_of_sink_nodes, path_prefix + (n,)))
  return set_of_result_paths

Это должно быть доказуемо правильным (но я не собираюсь делать доказательства, потому что уже очень поздно, и я устал и нечетко ;-) и пригоден для проверки любых дальнейших оптимизаций ;-).

Первая оптимизация, которую я бы попробовал, была бы своего рода простым запоминанием: если я уже вычислил набор путей от некоторого узла N к любому целевому узлу (независимо от префикса, ведущего к N, когда я выполнял это вычисление), я могу спрятать это в dict под ключом N и избежать дальнейших перерасчетов, если и когда я снова доберусь до N другим маршрутом ;-).

person Alex Martelli    schedule 19.07.2010
comment
Я бы проголосовал за ваш пост за его крутизну, но я чувствую себя обязанным сначала проверить его правильность. - person Eric Walker; 19.07.2010