Быстрый алгоритм подсчета количества ациклических путей на ориентированном графе - PullRequest
10 голосов
/ 06 апреля 2011

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

По простому графу, я имею в виду один безсобственные петли или несколько ребер. путь может начинаться с любого узла и должен заканчиваться на узле, который не имеет исходящих ребер.Путь имеет значение ациклический , если в нем нет ребер дважды.

Мои графы (эмпирические наборы данных) имеют только от 20 до 160 узлов, однако некоторые из них содержат много циклов, поэтомубудет очень большое количество путей, и мой наивный подход просто не достаточно быстр для некоторых графов, которые у меня есть.

В настоящее время я занимаюсь «спуском» по всем возможным ребрам с использованием рекурсивногофункция, сохраняя при этом, какие узлы я уже посетил (и избегая их).Самое быстрое решение, которое у меня было до сих пор, было написано на C ++ и использует аргумент std :: bitset в рекурсивной функции, чтобы отслеживать, какие узлы уже были посещены (посещенные узлы отмечены битом 1).Эта программа запускается на образце набора данных за 1-2 минуты (в зависимости от скорости компьютера).Для других наборов данных выполнение занимает более суток или, по-видимому, намного дольше.

Пример набора данных: http://pastie.org/1763781 (каждая строка представляет собой пару ребер)

Решение дляпримерный набор данных (первое число - это узел, с которого я начинаю, второе число - это количество путей, начинающихся с этого узла, последнее число - это общее количество путей): http://pastie.org/1763790

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

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

Ответы [ 3 ]

9 голосов
/ 06 апреля 2011

Это # P-полная, кажется.(ref http://www.maths.uq.edu.au/~kroese/ps/robkro_rev.pdf). Ссылка имеет приближение

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

.
4 голосов
/ 06 апреля 2011

Как уже упоминалось в spinning_plate, эта проблема # P-полная, поэтому начните искать свои аппроксимации :). Мне действительно нравится доказательство полноты # P для этой проблемы, поэтому я думаю, что было бы неплохо поделиться им:

Пусть N - количество путей (начиная с s ) в графе, а p_k - количество путей длины k . У нас есть:

N = p_1 + p_2 + ... + p_n

Теперь создайте второй граф, изменив каждое ребро на пару ребер параллеля. Для каждого пути длиной k теперь будет k ^ 2 путей, поэтому:

N_2 = p_1*2 + p_2*4 + ... + p_n*(2^n)

Повторение этого процесса, но с i ребрами вместо 2, выше n, даст нам линейную систему (с матрицей Вандермонда), позволяющую найти p_1, ..., p_n.

N_i = p_1*i + p_2*(i^2) + ...

Следовательно, найти число путей в графе так же сложно, как и найти число путей определенной длины. В частности, p_n - это число гамильтоновых путей (начиная с s ), истинная # P-полная задача.


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


Примечание: в большинстве случаев эта проблема обсуждается, пути начинаются с одного ребра и заканчиваются где угодно. Это противоположно вашей проблеме, но вы должны быть эквивалентны, просто перевернув все края.

0 голосов
/ 18 февраля 2017

Важность постановки задачи

Неясно, что подсчитывается.

  1. Установлен ли начальный узел все узлы, для которых имеется хотя бы один исходящий фронт, или есть определенные критерии начального узла?
  2. Является ли конечный узел набором всех узлов, для которых имеются нулевые исходящие ребра, или может ли любой узел, для которого есть хотя бы один входящий ребро, быть возможным конечным узлом?

Определите вашу проблему, чтобы не было двусмысленности.

Оценка

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

Два оптимизирующих пункта

Модель std :: bitset будет медленнее, чем значения bool для большинства архитектур процессоров, из-за механики набора команд для тестирования бита с определенным смещением бита. Набор битов более полезен, когда объем памяти, а не скорость, является критическим фактором.

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

Использование кластеров

Проблема может быть выполнена в кластере путем распределения по начальному узлу. Некоторые проблемы просто требуют суперкомпьютеров. Если у вас есть 1 000 000 начальных узлов и 10 процессоров, вы можете разместить 100 000 начальных узлов на каждом процессоре. Вышеупомянутые исключения и сокращения случая должны быть сделаны до распределения случаев.

Типичная первая глубина рекурсии и как ее оптимизировать

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

#include <iostream>
#include <list>

class DirectedGraph {

    private:
        int miNodes;
        std::list<int> * mnpEdges;
        bool * mpVisitedFlags;

    private:
        void initAlreadyVisited() {
            for (int i = 0; i < miNodes; ++ i)
                mpVisitedFlags[i] = false;
        }

        void recurse(int iCurrent, int iDestination,
               int path[], int index,
               std::list<std::list<int> *> * pnai) {

            mpVisitedFlags[iCurrent] = true;
            path[index ++] = iCurrent;

            if (iCurrent == iDestination) {
                auto pni = new std::list<int>;
                for (int i = 0; i < index; ++ i)
                    pni->push_back(path[i]);
                pnai->push_back(pni);

            } else {
                auto it = mnpEdges[iCurrent].begin();
                auto itBeyond = mnpEdges[iCurrent].end();
                while (it != itBeyond) {
                    if (! mpVisitedFlags[* it])
                        recurse(* it, iDestination,
                                path, index, pnai);
                    ++ it;
                }
            }

            -- index;
            mpVisitedFlags[iCurrent] = false;
        } 

    public:
        DirectedGraph(int iNodes) {
            miNodes = iNodes;
            mnpEdges = new std::list<int>[iNodes];
            mpVisitedFlags = new bool[iNodes];
        }

        ~DirectedGraph() {
            delete mpVisitedFlags;
        }

        void addEdge(int u, int v) {
            mnpEdges[u].push_back(v);
        }

        std::list<std::list<int> *> * findPaths(int iStart,
                int iDestination) {
            initAlreadyVisited();
            auto path = new int[miNodes];
            auto pnpi = new std::list<std::list<int> *>();
            recurse(iStart, iDestination, path, 0, pnpi);
            delete path;
            return pnpi;
        }
};

int main() {

    DirectedGraph dg(5);

    dg.addEdge(0, 1);
    dg.addEdge(0, 2);
    dg.addEdge(0, 3);
    dg.addEdge(1, 3);
    dg.addEdge(1, 4);
    dg.addEdge(2, 0);
    dg.addEdge(2, 1);
    dg.addEdge(4, 1);
    dg.addEdge(4, 3);

    int startingNode = 0;
    int destinationNode = 1;

    auto pnai = dg.findPaths(startingNode, destinationNode);

    std::cout
            << "Unique paths from "
            << startingNode
            << " to "
            << destinationNode
            << std::endl
            << std::endl;

    bool bFirst;
    std::list<int> * pi;
    auto it = pnai->begin();
    auto itBeyond = pnai->end();
    std::list<int>::iterator itInner;
    std::list<int>::iterator itInnerBeyond;
    while (it != itBeyond) {
        bFirst = true;
        pi = * it ++;
        itInner = pi->begin();
        itInnerBeyond = pi->end();
        while (itInner != itInnerBeyond) {
            if (bFirst)
                bFirst = false;
            else
                std::cout << ' ';
            std::cout << (* itInner ++);
        }
        std::cout << std::endl;
        delete pi;
    }

    delete pnai;

    return 0;
}
...