Подтвердить что ты не робот

Хороший алгоритм обхода графа

Абстрактная проблема: у меня есть график около 250 000 узлов, а средняя связь - около 10. Поиск соединений node - длительный процесс (скажем, через 10 секунд). Сохранение node в базе данных также занимает около 10 секунд. Я могу проверить, действительно ли node уже присутствует в db. Разрешая concurrency, но не имея более 10 длинных запросов за раз, как бы вы пересекли график, чтобы получить максимальный охват быстрее.

Конкретная проблема: я пытаюсь очистить страницы пользователя веб-сайта. Чтобы узнать новых пользователей, я получаю список друзей от уже известных пользователей. Я уже импортировал около 10% графика, но я постоянно зацикливаюсь или слишком много памяти запоминаю слишком много узлов.

Моя текущая реализация:

def run() :
    import_pool = ThreadPool(10)
    user_pool = ThreadPool(1)
    do_user("arcaneCoder", import_pool, user_pool)

def do_user(user, import_pool, user_pool) :
    id = user
    alias = models.Alias.get(id)

    # if its been updates in the last 7 days
    if alias and alias.modified + datetime.timedelta(days=7) > datetime.datetime.now() :
        sys.stderr.write("Skipping: %s\n" % user)
    else :
        sys.stderr.write("Importing: %s\n" % user)
        while import_pool.num_jobs() > 20 :
            print "Too many queued jobs, sleeping"
            time.sleep(15)

        import_pool.add_job(alias_view.import_id, [id], lambda rv : sys.stderr.write("Done Importing %s\n" % user))

    sys.stderr.write("Crawling: %s\n" % user)
    users = crawl(id, 5)
    if len(users) >= 2 :
        for user in random.sample(users, 2) :
            if (user_pool.num_jobs() < 100) :
                user_pool.add_job(do_user, [user, import_pool, user_pool])

def crawl(id, limit=50) :
    '''returns the first 'limit' friends of a user'''
    *not relevant*

Проблемы текущей реализации:

  • Возвращается в клики, которые я уже импортировал, тем самым теряя время, а импортирующие потоки простаивают.
  • Будет добавлено больше, поскольку они будут указаны.

Итак, маргинальные улучшения приветствуются, а также полные перезаписи. Спасибо!

4b9b3361

Ответ 1

Чтобы помнить идентификаторы пользователей, которые вы уже посетили, вам нужна карта длиной 250 000 целых чисел. Это далеко не "слишком много". Просто поддерживайте такую ​​карту и проходите только по краям, которые ведут к уже неоткрытым пользователям, добавляя их к этой карте в точке нахождения такого края.

Насколько я могу судить, вы близки к реализации поиска Breadth-first (BFS). Проверьте google о деталях этого алгоритма. И, конечно же, не забывайте о мьютексах - они вам понадобятся.

Ответ 2

Я действительно смущен тем, почему требуется 10 секунд, чтобы добавить node в БД. Это звучит как проблема. Какую базу данных вы используете? У вас серьезные ограничения на платформу?

В современных системах и их кучах памяти я бы рекомендовал хороший простой кеш. Вы должны иметь возможность создавать очень быстрый кеш информации пользователя, который позволит вам избежать повторной работы. Когда вы уже столкнулись с node, прекратите обработку. Это позволит избежать цикличности навсегда в кликах.

Если вам нужно разрешить повторное использование существующих узлов через некоторое время, вы можете использовать last_visit_number, который будет глобальным значением в дБ. Если node имеет это число, то этот обход - тот, который столкнулся с ним. Если вы хотите автоматически пересмотреть любые узлы, вам просто нужно нанести последний_визуальный номер перед запуском обхода.

По вашему описанию я не совсем уверен, как вы застреваете.

Изменить ------ Я просто заметил, что у вас есть конкретный вопрос. Чтобы увеличить скорость получения новых данных, я буду отслеживать количество подключений данного пользователя в ваших данных (импортированных или еще не импортированных). Когда вы выбираете пользователя для сканирования, я бы выбрал пользователей с низким количеством ссылок. Я бы специально использовал либо самое низкое количество ссылок, либо случайный выбор среди пользователей с наименьшим количеством ссылок.

Jacob

Ответ 3

Нет конкретного алгоритма, который поможет вам оптимизировать построение графика с нуля. Так или иначе, вам придется посещать каждый node хотя бы один раз. Выполняете ли вы это глубину в начале или ширина сначала не имеет значения с точки зрения скорости. Theran правильно указывает в комментарии ниже, что поиск по ширине, сначала исследуя более близкие узлы, может дать вам более полезный график сразу, прежде чем весь граф будет завершено; это может быть или не быть проблемой для вас. Он также отмечает, что самая аккуратная версия поиска по глубине выполняется с использованием рекурсии, что потенциально может быть проблемой для вас. Обратите внимание, что рекурсия не требуется; вы можете добавить не полностью исследованные узлы в стек и обработать их линейно, если хотите.

Если вы выполняете обычную проверку наличия новых узлов (O (1), если вы используете хэш для поиска), то циклы вообще не будут проблемой. Циклы - это только проблема, если вы не храните полный график. Вы можете оптимизировать поиск по графику, но сам этап построения всегда будет занимать линейное время.

Я согласен с другими плакатами, что размер вашего графика не должен быть проблемой. 250 000 не очень большой!

Относительно одновременного выполнения; график обновляется всеми потоками, поэтому он должен быть синхронизированной структурой данных. Поскольку это Python, вы можете использовать модуль Queue для хранения новых ссылок, которые еще обрабатываются вашими потоками.

Ответ 4

Хотя вы говорите, что получение списка друзей занимает много времени (10 секунд и более), вариант доброжелательного алгоритма Дейкстры может работать:

  • Получить любой node.
  • Получить соединение с любым загруженным node.
  • Если другой конец еще не загружен, добавьте node.
  • Перейдите к шагу 2.

Фокус в том, чтобы выбрать соединение, которое вы загружаете на шаге 2, умным способом. Несколько коротких замечаний об этом:

  • Вы должны каким-то образом запретить загрузку одного и того же соединения дважды или чаще. Выбор случайного соединения и сброс его, если он уже загружен, очень неэффективен, если вы после всех подключений.
  • Если вы хотите в любой момент загрузить все соединения, загрузите все соединения node одновременно.

Чтобы действительно сказать что-то об эффективности, предоставьте более подробную информацию о структуре данных.