Поиск (гарантированно уникального) пути между двумя узлами в дереве

У меня есть (вероятно) простой вопрос обхода графа. Я новичок в графике, используя networkx в качестве структур данных графа. Мои графики всегда выглядят так:

             0
      1              8
   2     3       9      10
 4  5   6 7    11 12  13  14

Мне нужно вернуть путь от корневого узла к заданному узлу (например, path(0, 11) должно вернуть [0, 8, 9, 11]).

У меня есть решение, которое работает, передавая список, который растет и сжимается, чтобы отслеживать, как выглядит путь, когда вы проходите по дереву, и в конечном итоге возвращается, когда целевой узел найден:

def VisitNode(self, node, target, path):
    path.append(node)
    # Base case. If we found the target, then notify the stack that we're done.
    if node == target:
        return True
    else:
        # If we're at a leaf and it isn't the target, then pop the leaf off
        # our path (backtrack) and notify the stack that we're still looking
        if len(self.neighbors(node)) == 0:
            path.pop()
            return False
        else:
            # Sniff down the next available neighboring node
            for i in self.neighbors_iter(node):
                # If this next node is the target, then return the path 
                # we've constructed so far
                if self.VisitNode(i, target, path):
                    return path
            # If we've gotten this far without finding the target, 
            # then this whole branch is a dud. Backtrack
            path.pop()

Я чувствую в своих костях, что нет необходимости передавать этот список "путей"... Я должен иметь возможность отслеживать эту информацию, используя стек вызовов, но я не могу понять, как... Может кто-нибудь просветить мне о том, как бы вы рекурсивно решили эту проблему, используя стек для отслеживания пути?


person paradiso    schedule 28.09.2013    source источник
comment
Обязательное чтение для реализации графиков в python python.org/doc/essays/graphs   -  person Fredrik Pihl    schedule 29.09.2013
comment
Ответ, вероятно, здесь: stackoverflow.com/questions/6061744/   -  person GL770    schedule 29.09.2013
comment
@ GL770 Прямой просмотр стека с использованием модуля inspect для такого простого алгоритма графа кажется мне не очень элегантным.   -  person Bas Swinckels    schedule 29.09.2013
comment
@ GL770 - Это хорошая идея, но не очень элегантная. Как правило, я бы хотел, чтобы содержимое стека выпадало из каскада операторов «возврата», а не путем прямой проверки.   -  person paradiso    schedule 29.09.2013


Ответы (3)


Вы можете избежать обхода пути, возвращая None в случае ошибки и частичный путь в случае успеха. Таким образом, вы не сохраняете своего рода «цепочку навигации» от корня к текущему узлу, а только строите путь от цели обратно к корню, если найдете его. Непроверенный код:

def VisitNode(self, node, target):
    # Base case. If we found the target, return target in a list
    if node == target:
        return [node]

    # If we're at a leaf and it isn't the target, return None 
    if len(self.neighbors(node)) == 0:
        return None

    # recursively iterate over children
    for i in self.neighbors_iter(node):
        tail = self.VisitNode(i, target)
        if tail: # is not None
            return [node] + tail # prepend node to path back from target
    return None #none of the children contains target

Я не знаю, какую графическую библиотеку вы используете, но я предполагаю, что даже листы содержат метод neighbours_iter, который, очевидно, не должен давать дочерних элементов для листа. В этом случае вы можете опустить явную проверку листа, сделав ее немного короче:

def VisitNode(self, node, target):
    # Base case. If we found the target, return target in a list
    if node == target:
        return [node]
    # recursively iterate over children
    for i in self.neighbors_iter(node):
        tail = self.VisitNode(i, target)
        if tail: # is not None
            return [node] + tail # prepend node to path back from target
    return None # leaf node or none of the child contains target

Я также удалил некоторые операторы else, так как внутри истинной части if вы возвращаетесь из функции. Это распространенный шаблон рефакторинга (который не нравится некоторым приверженцам старой школы). Это удаляет некоторые ненужные отступы.

person Bas Swinckels    schedule 28.09.2013
comment
Именно то, на что я надеялся. Спасибо за вашу помощь... Я играл с этой идеей, но у меня она не сложилась. (P.S. Я протестировал его, и он работает, как описано, хотя у вас есть трейлинг: в строке 5, который нужно удалить) - person paradiso; 29.09.2013

Вы можете вообще избежать аргумента пути, инициализировав путь в теле метода. Если метод возвращается до того, как найдет полный путь, он может вернуть пустой список.

Но ваш вопрос также касается использования стека вместо списка в реализации поиска в глубину, верно? Здесь вы получите представление: http://en.literateprograms.org/Depth-first_search_%28Python%29.

Короче говоря, вы

def depthFirstSearch(start, isGoal, result):
    ###ensure we're not stuck in a cycle

    result.append(start)

    ###check if we've found the goal
    ###expand each child node in order, returning if we find the goal

    # No path was found
    result.pop()
    return False

с

###<<expand each child node in order, returning if we find the goal>>=
for v in start.successors:
    if depthFirstSearch(v, isGoal, result):
        return True

а также

###<<check if we've found the goal>>=
if isGoal(start):
    return True
person kiriloff    schedule 28.09.2013
comment
Спасибо за ваш ответ. Это хорошее объяснение принятого ответа (в основном они эквивалентны). Всегда хорошо иметь больше ракурсов. +1 - person paradiso; 29.09.2013

Используйте networkx напрямую:

all_simple_paths(G, источник, цель, отсечение=нет)

person denfromufa    schedule 06.10.2013
comment
Не могли бы вы предоставить пример кода с жестко закодированным графом и тестовым набором? - person Tanishq Vyas; 06.04.2020