Можно ли сделать этот поиск в ширину быстрее? - PullRequest
5 голосов
/ 18 ноября 2009

У меня есть набор данных, который представляет собой большой невзвешенный циклический граф. Циклы происходят в циклах по 5-6 путей. Он состоит из примерно 8000 узлов, и каждый узел имеет от 1 до 6 (обычно около 4-5) соединений. Я выполняю вычисления кратчайшего пути в одной паре и реализовал следующий код для поиска в ширину.

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

Приведенный выше код использует модуль очереди Python 2.6, а getNeighbours () - это просто подпрограмма, которая выполняет один вызов MySQL и возвращает соседей в виде списка кортежей, например, (( 'Foo'), ( 'бар')). Вызов SQL быстрый.

Код работает нормально, однако тестирование до глубины около 7 уровней занимает около 20 секунд (2,5 ГГц Intel 4GB RAM OS X 10.6)

Буду рад любым комментариям о том, как улучшить время выполнения этого кода.

Ответы [ 4 ]

11 голосов
/ 18 ноября 2009

Что ж, учитывая положительные отзывы на комментарий, я сейчас сделаю ответ.

SQL в узком цикле определенно замедляет работу. Мне все равно, как быстро звонок. Подумайте об этом - вы просите проанализировать запрос, выполнить поиск - так быстро, как он есть, он все еще находится в узком цикле. Как выглядит ваш набор данных? Можете ли вы просто SELECT весь набор данных в памяти или хотя бы работать с ним вне MySQL?

Если вы работаете с этими данными в памяти, вы увидите значительное повышение производительности.

1 голос
/ 18 ноября 2009

Примерно так:

#!/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']

Если вы замените ваши SQL-запросы словарем списков (и это было бы непросто), вы получите такую ​​производительность.

0 голосов
/ 18 ноября 2009

Хм, разве BFS не включает в себя маркировку узлов, которые вы уже видели, чтобы вы больше не посещали их?

0 голосов
/ 18 ноября 2009

Держу пари, что у машины более одного ядра, не так ли? Запустите его параллельно.

Python Threading

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...