Kapsamlı Arama'da yol nasıl izlenir?


105

Aşağıdaki örnekte olduğu gibi, Genişlik İlk Aramanın yolunu nasıl izlersiniz:

Anahtar aranıyorsa11 , 1'den 11'e bağlanan en kısa listeye dönün .

[1, 4, 7, 11]

6
Aslında Kevin Bacon Yasasına göre aylar önce bir arkadaşıma yardım ettiğim eski bir görevdi. Nihai çözümüm çok özensizdi, temelde "geri sarmak" ve geri izlemek için başka bir Önce Genişlik araması yaptım. Daha iyi bir çözüm bulamayacağım.
Christopher Markieta

21
Mükemmel. Bir mühendisde takdire şayan bir özellik olması için daha iyi bir yanıt bulma çabasıyla eski bir sorunu tekrar gözden geçirmeyi düşünüyorum. Çalışmalarınızda ve kariyerinizde başarılar dilerim.
Peter Rowell

1
Övgü için teşekkürler, inanıyorum ki şimdi öğrenmezsem, yine aynı problemle karşılaşacağım.
Christopher Markieta

Yanıtlar:


195

Önce http://en.wikipedia.org/wiki/Breadth-first_search'e bakmalısınız .


Aşağıda, yolların sırasını temsil etmek için bir liste listesi kullandığım hızlı bir uygulama verilmiştir.

# 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')

Diğer bir yaklaşım, her bir düğümden ebeveynine bir eşlemeyi sürdürmek ve bitişik düğümü incelerken, ebeveynini kaydetmektir. Arama yapıldığında, ana eşlemeye göre geriye doğru izleme yapın.

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')

Yukarıdaki kodlar, döngü olmadığı varsayımına dayanmaktadır.


2
Bu mükemmel! Düşünce sürecim beni bir tür tablo veya matris oluşturmaya inanmaya yöneltti, henüz grafikler hakkında bilgi edinmedim. Teşekkür ederim.
Christopher Markieta

Bu çok daha temiz görünmesine rağmen, geri izleme yaklaşımı kullanmayı da denedim. Yalnızca başlangıcı ve sonu bilirseniz ancak aradaki düğümlerden hiçbirini bilmiyorsanız bir grafik yapmak mümkün olur mu? Ya da grafiklerin yanı sıra başka bir yaklaşım mı?
Christopher Markieta

1
İlk algoritmayı 1'den 11'e kadar tüm yolları döndürecek şekilde uyarlamak mümkün müdür (birden fazla olduğu varsayılarak)?
Maria Ines Parnisari

1
@ l19 Bir yol ( node==end) bulduğunuzda, o yolu bulduğunuz tüm yolları içeren başka bir listeye ekleyin, sonra continueyerine return. Döngüleri önlemek için ziyaret edilmiş bir küme kullanıyorsanız, son düğümünüzü ziyaret edilen kümeye hiçbir zaman eklemeyin (aksi takdirde yalnızca bir yol bu son düğüme sahip olabilir).
Dominic K

1
Liste yerine collections.deque kullanmanız önerilir. list.pop (0) 'ın karmaşıklığı O (n) iken deque.popleft () ise O (1)
Omar_0x80

23

Qiao'nun ilk cevabını çok beğendim! Burada eksik olan tek şey, köşeleri ziyaret edilmiş olarak işaretlemektir.

Bunu neden yapmamız gerekiyor?
11 numaralı düğümden bağlanan başka bir 13 numaralı düğüm olduğunu düşünelim. Şimdi amacımız 13. düğümü bulmak.
Biraz çalıştıktan sonra sıra şu şekilde görünecek:

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

Sonunda düğüm numarası 10 olan İKİ yol olduğunu unutmayın.
Bu, 10 numaralı düğümden gelen yolların iki kez kontrol edileceği anlamına gelir. Bu durumda o kadar da kötü görünmüyor çünkü 10 numaralı düğümün çocuğu yok .. Ama gerçekten kötü olabilir (burada bile sebepsiz yere bu düğümü iki kez kontrol edeceğiz ..)
13 numaralı düğüm içinde değil bu yollar, böylece program, sonunda 10 numaralı düğüm olan ikinci yola ulaşmadan geri dönmeyecek ... Ve onu yeniden kontrol edeceğiz ..

Tek eksiğimiz, ziyaret edilen düğümleri işaretlemek ve tekrar kontrol etmemek için bir set.
Bu, qiao'nun değişiklikten sonraki kodu:

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)

Programın çıktısı:

[1, 4, 7, 11, 13]

Gereksiz kontroller olmadan ..


6
Kullanımı yararlı olabilir collections.dequeiçin queuelist.pop olarak (0) tahakkuk O(n)bellek hareketler. Ayrıca, gelecek nesiller için, DFS yapmak istiyorsanız, sadece path = queue.pop()bu durumda değişken queuefiilen bir stack.
Sudhi

11

Çok kolay kod. Her düğüm keşfettiğinizde yolu eklemeye devam edersiniz.

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
Kodunuzu diğer yanıtlara kıyasla çok okunaklı buluyorum. Çok teşekkür ederim!
Mitko Rusev

8

Bunu eğlence için kodlamayı deneyeyim dedim:

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

Döngüleri istiyorsanız, bunu ekleyebilirsiniz:

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

next_for_front'u oluşturduktan sonra. Bir takip sorusu, ya grafik döngüler içeriyorsa? Örneğin, düğüm 1'in kendisine geri bağlanan bir kenarı varsa? Ya grafiğin iki düğüm arasında giden birden fazla kenarı varsa?
robert king

1

Hem @Qiao ilk yanıtı hem de @ Or'un eklenmesini seviyorum. Biraz daha az işlem yapmak adına Or'un cevabına eklemek istiyorum.

@ Or'un cevabında ziyaret edilen düğümü takip etmek harika. Programın şu anda olduğundan daha erken çıkmasına da izin verebiliriz. For döngüsünün bir noktasında, current_neighbouro olmak zorunda olacaktır endve bu gerçekleştiğinde en kısa yol bulunur ve program geri dönebilir.

Yöntemi aşağıdaki gibi değiştirirdim, for döngüsüne çok dikkat edin

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)

Çıktı ve diğer her şey aynı olacak. Ancak kodun işlenmesi daha az zaman alacaktır. Bu özellikle daha büyük grafiklerde kullanışlıdır. Umarım bu gelecekte birine yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.