2009-11-18 16 views
5

Ich habe einen Datensatz, der ein großer ungewichteter zyklischer Graph ist. Die Zyklen treten in Schleifen von ungefähr 5-6 Pfaden auf. Es besteht aus ungefähr 8000 Knoten und jeder Knoten hat 1-6 (normalerweise 4-5) Verbindungen. Ich mache Single Pair Shortest Path-Berechnungen und habe den folgenden Code implementiert, um eine Breitensuche durchzuführen.Kann diese Breitensuche schneller gemacht werden?

from Queue import Queue 

q = Queue() 
parent = {} 
fromNode = 'E1123' 
toNode = 'A3455' 

# path finding 
q.put(fromNode) 
parent[fromNode] = 'Root' 

while not q.empty(): 
    # get the next node and add its neighbours to queue 
    current = q.get() 
    for i in getNeighbours(current): 
    # note parent and only continue if not already visited 
    if i[0] not in parent: 
     parent[i[0]] = current 
     q.put(i[0]) 

    # check if destination 
    if current == toNode: 
    print 'arrived at', toNode 
    break 

Der obige Code verwendet die Python 2.6 Queue-Modul und getNeighbours() ist einfach eine Unterroutine, die einen einzelnen MySQL Anruf tätigt und gibt die Nachbarn als eine Liste von Tupeln z.B. (('foo',), ('bar',)). Der SQL-Aufruf ist schnell.

Der Code funktioniert ok Prüfung jedoch nach unten von etwa 7 Schichten bis in Tiefen dauert etwa 20 Sekunden (2,5 GHz Intel 4GB RAM OS X 10.6)

Ich würde

laufen begrüßen alle Kommentare darüber, wie den Lauf zu verbessern Zeit dieses Codes.

Antwort

11

Nun, angesichts der Upvotes auf den Kommentar, werde ich es jetzt eine Antwort machen.

Die SQL in der engen Schleife ist definitiv verlangsamt Sie. Es ist mir egal, wie schnell der Anruf ist. Denken Sie darüber nach - Sie verlangen, dass eine Abfrage geparst wird, eine Suche ausgeführt wird - so schnell wie das ist, ist es immer noch in einer engen Schleife. Wie sieht Ihr Datensatz aus? Kannst du einfach SELECT den gesamten Datensatz in den Speicher schreiben, oder zumindest damit außerhalb von MySQL arbeiten?

Wenn Sie mit diesen Daten im Speicher arbeiten, werden Sie eine deutliche Leistungssteigerung feststellen.

+0

abgeordnet, 8000 Knoten werden leicht in den Speicher passen. –

+0

Guter Anruf! Die Tabelle mit den Knoteninformationen ist einfach Zeilen von fromNode, toNode. Ich werde untersuchen, es einfach in den Speicher zu laden .. vielleicht nur eine große Dictionary-Struktur. – timbo

+3

Als einfachen Test habe ich das MySQL in den Speicher geladen, indem ich einfach ENGINE = MEMORY für die CREATE TABLE-Definition verwende. Derselbe Code ist jetzt in etwa 2,5 Sekunden fertig! – timbo

0

Ich wette, dass die Maschine mehr als einen Kern hat, oder? Führen Sie es parallel aus.

Python Threading

+3

Ich denke, du meinst Python Multiprocessing. –

0

Hmm, beinhaltet keine BFS Markierung Knoten Sie bereits gesehen haben, so dass Sie sie nicht wieder besuchen?

+0

Es tut. Wenn der Knoten bereits in übergeordnetes [] platziert wurde, wurde er bereits vorher gesehen und wird daher nicht in die Warteschlange gestellt, um erneut zu agieren. – timbo

+0

Oh, ich verstehe. Das habe ich vermisst, Entschuldigung. Dennoch wäre es billiger, diese Flagge in jeden Knoten zu legen, als einen immer größer werdenden Satz zu manipulieren. –

1

Etwas wie folgt aus:

#!/usr/bin/env python 

from Queue import Queue 

def traverse_path(fromNode, toNode, nodes): 
    def getNeighbours(current, nodes): 
     return nodes[current] if current in nodes else [] 

    def make_path(toNode, graph): 
     result = [] 
     while 'Root' != toNode: 
      result.append(toNode) 
      toNode = graph[toNode] 
     result.reverse() 
     return result 

    q = Queue() 
    q.put(fromNode) 
    graph = {fromNode: 'Root'} 

    while not q.empty(): 
     # get the next node and add its neighbours to queue 
     current = q.get() 
     for neighbor in getNeighbours(current, nodes): 
      # use neighbor only continue if not already visited 
      if neighbor not in graph: 
       graph[neighbor] = current 
       q.put(neighbor) 

     # check if destination 
     if current == toNode: 
      return make_path(toNode, graph) 
    return [] 

if __name__ == '__main__': 
    nodes = { 
     'E1123': ['D111', 'D222', 'D333', 'D444'], 
     'D111': ['C01', 'C02', 'C04'], 
     'D222': ['C11', 'C03', 'C05'], 
     'D333': ['C01'], 
     'C02': ['B1'], 
     'B1': ['A3455'] 
    } 
    result = traverse_path('E1123', 'A3455', nodes) 
    print result 

['E1123', 'D111', 'C02', 'B1', 'A3455'] 

Wenn Sie Ihre SQL-Abfragen mit einem Wörterbuch von Listen ersetzen (und das wäre der schwierige Teil sein), werden Sie diese Leistung erzielen.

+0

Super Hugh. Ich habe eine gute Leistung mit einer In-Memory-Tabelle bekommen. Ich werde den nächsten Schritt versuchen. Es gibt ungefähr 14K Verbindungen und dies wird schließlich eine Web-App sein, so dass ich sorgfältig darüber nachdenken muss, wie die Ladung in den Speicher funktioniert. – timbo

+0

Ich habe festgestellt, dass mein Code ähnlich wie Ihr Hugh aber Ihr ist sicherlich eleganter.Abgesehen von einer locale-abhängigen Schreibweise von 'Nachbar/Nachbar' ist der einzige zusätzliche Vorschlag, den ich für das obige hätte, der Aufruf von result.reverse() in make_path, da dies eine Liste in der Reihenfolge fromNode -> toNode – timbo

+0

Hmmmm zurückgibt. Das ist eine gute Idee. Ich füge das hinzu. – hughdbrown

Verwandte Themen