Breadth-First Search에서 경로를 추적하는 방법은 무엇입니까?


104

다음 예에서와 같이 Breadth-First Search의 경로를 어떻게 추적합니까?

key를 검색하면 1에서 11까지 연결 11하는 가장 짧은 목록을 반환합니다 .

[1, 4, 7, 11]

6
케빈 베이컨 법에 따라 몇 달 전에 친구를 돕고 있었던 것은 사실 오래된 임무였습니다. 내 최종 솔루션은 매우 조잡했습니다. 기본적으로 "되감기"와 역 추적을 위해 또 다른 Breadth 우선 검색을 수행했습니다. 더 나은 해결책을 찾고 싶지 않습니다.
Christopher Markieta

21
우수한. 엔지니어의 훌륭한 특성에 대한 더 나은 답을 찾기 위해 오래된 문제를 다시 검토하는 것을 고려합니다. 학업과 경력이 잘 되길 바랍니다.
Peter Rowell

1
칭찬 해주셔서 감사합니다. 지금 배우지 않으면 다시 같은 문제에 직면하게 될 것이라고 믿습니다.
크리스토퍼 Markieta

답변:


194

먼저 http://en.wikipedia.org/wiki/Breadth-first_search를 봐야 합니다.


아래는 목록 목록을 사용하여 경로 대기열을 나타내는 빠른 구현입니다.

# graph is in adjacent list representation
graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, start, end):
    # maintain a queue of paths
    queue = []
    # push the first path into the queue
    queue.append([start])
    while queue:
        # get the first path from the queue
        path = queue.pop(0)
        # get the last node from the path
        node = path[-1]
        # path found
        if node == end:
            return path
        # enumerate all adjacent nodes, construct a new path and push it into the queue
        for adjacent in graph.get(node, []):
            new_path = list(path)
            new_path.append(adjacent)
            queue.append(new_path)

print bfs(graph, '1', '11')

또 다른 접근 방식은 각 노드에서 부모로의 매핑을 유지하고 인접 노드를 검사 할 때 부모를 기록하는 것입니다. 검색이 완료되면 상위 매핑에 따라 역 추적하면됩니다.

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def backtrace(parent, start, end):
    path = [end]
    while path[-1] != start:
        path.append(parent[path[-1]])
    path.reverse()
    return path


def bfs(graph, start, end):
    parent = {}
    queue = []
    queue.append(start)
    while queue:
        node = queue.pop(0)
        if node == end:
            return backtrace(parent, start, end)
        for adjacent in graph.get(node, []):
            if node not in queue :
                parent[adjacent] = node # <<<<< record its parent 
                queue.append(adjacent)

print bfs(graph, '1', '11')

위의 코드는 사이클이 없다는 가정을 기반으로합니다.


2
이것은 훌륭합니다! 내 생각 과정은 어떤 유형의 테이블이나 행렬을 만드는 것을 믿게 만들었지 만 아직 그래프에 대해 배우지 않았습니다. 감사합니다.
Christopher Markieta 2012 년

나는 또한 이것이 훨씬 깨끗해 보이지만 역 추적 접근 방식을 사용해 보았습니다. 시작과 끝만 알고 중간 노드가없는 경우 그래프를 만들 수 있습니까? 아니면 그래프 외에 다른 접근 방식?
Christopher Markieta 2012 년

@ChristopherM 나는 당신의 질문을 이해하지 못했습니다 :(
qiao

1
첫 번째 알고리즘을 적용하여 1에서 11까지의 모든 경로를 반환하도록 할 수 있습니까 (둘 이상이라고 가정)?
Maria Ines Parnisari 2014 년

1
목록 대신 collections.deque를 사용하는 것이 좋습니다. list.pop (0)의 복잡성은 O (n)이고 deque.popleft ()는 O (1)
Omar_0x80

23

나는 qiao의 첫 번째 대답을 매우 좋아했습니다! 여기서 빠진 유일한 것은 꼭지점을 방문한 것으로 표시하는 것입니다.

왜해야합니까?
노드 11에서 연결된 다른 노드 번호 13이 있다고 가정 해
봅시다 . 이제 우리의 목표는 노드 13을 찾는 것입니다 . 약간의 실행 후 큐는 다음과 같이 보일 것입니다.

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]

끝에 노드 번호가 10 인 경로가 두 개 있습니다.
즉, 노드 번호 10의 경로가 두 번 확인됩니다. 이 경우에는 10 번 노드에 자식이 없기 때문에 그렇게 나쁘게 보이지는 않습니다.하지만 정말 나쁠 수 있습니다 (여기서도 이유없이 해당 노드를 두 번 확인합니다.)
노드 13 번이 없습니다. 마지막에 노드 번호가 10 인 두 번째 경로에 도달하기 전에 프로그램이 반환되지 않도록 해당 경로를 다시 확인합니다.

우리가 놓친 것은 방문한 노드를 표시하고 다시 확인하지 않도록 설정하는 것입니다.
이것은 수정 후 qiao의 코드입니다.

graph = {
    1: [2, 3, 4],
    2: [5, 6],
    3: [10],
    4: [7, 8],
    5: [9, 10],
    7: [11, 12],
    11: [13]
}


def bfs(graph_to_search, start, end):
    queue = [[start]]
    visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

프로그램의 출력은 다음과 같습니다.

[1, 4, 7, 11, 13]

불충분 한 재확인없이 ..


6
사용하기 유용 할 수 있습니다 collections.deque에 대한 queuelist.pop로 (0)이 발생할 O(n)메모리 운동. 또한 후손을 위해 DFS를 수행 path = queue.pop()하려면 변수가 queue실제로 stack.
Sudhi

11

매우 쉬운 코드. 노드를 발견 할 때마다 계속 경로를 추가합니다.

graph = {
         'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])
         }
def retunShortestPath(graph, start, end):

    queue = [(start,[start])]
    visited = set()

    while queue:
        vertex, path = queue.pop(0)
        visited.add(vertex)
        for node in graph[vertex]:
            if node == end:
                return path + [end]
            else:
                if node not in visited:
                    visited.add(node)
                    queue.append((node, path + [node]))

2
다른 답변에 비해 코드를 매우 읽기 쉽습니다. 대단히 감사합니다!
Mitko Rusev

8

나는 이것을 재미로 코딩하려고 생각했다.

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, forefront, end):
    # assumes no cycles

    next_forefront = [(node, path + ',' + node) for i, path in forefront if i in graph for node in graph[i]]

    for node,path in next_forefront:
        if node==end:
            return path
    else:
        return bfs(graph,next_forefront,end)

print bfs(graph,[('1','1')],'11')

# >>>
# 1, 4, 7, 11

주기를 원한다면 다음을 추가 할 수 있습니다.

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]

next_for_front를 구축 한 후. 후속 질문, 그래프에 루프가 포함되어 있으면 어떻게됩니까? 예를 들어 노드 1이 자신에게 다시 연결되는 에지를 가지고 있다면? 그래프에 두 노드 사이에 여러 간선이 있으면 어떻게됩니까?
로버트 왕

1

나는 @Qiao 첫 번째 답변과 @Or의 추가를 모두 좋아합니다. 약간의 처리를 위해 Or의 답변에 추가하고 싶습니다.

@Or의 답변에서 방문한 노드를 추적하는 것은 좋습니다. 또한 프로그램이 현재보다 빨리 종료되도록 할 수 있습니다. for 루프의 어느 시점에서는 current_neighbour이어야하며 end, 일단 발생하면 최단 경로가 발견되고 프로그램이 반환 될 수 있습니다.

다음과 같이 메서드를 수정하고 for 루프에 세심한주의를 기울입니다.

graph = {
1: [2, 3, 4],
2: [5, 6],
3: [10],
4: [7, 8],
5: [9, 10],
7: [11, 12],
11: [13]
}


    def bfs(graph_to_search, start, end):
        queue = [[start]]
        visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

                #No need to visit other neighbour. Return at once
                if current_neighbour == end
                    return new_path;

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

출력과 다른 모든 것은 동일합니다. 그러나 코드를 처리하는 데 시간이 덜 걸립니다. 이것은 큰 그래프에서 특히 유용합니다. 나는 이것이 미래의 누군가에게 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.