簡體   English   中英

用於計算有向圖上非循環路徑數的快速算法

[英]Fast algorithm for counting the number of acyclic paths on a directed graph

簡而言之,我需要一個快速算法來計算簡單有向圖中有多少非循環路徑。

通過簡單的圖表我的意思是沒有自循環或多個邊緣。 路徑可以從任何節點開始,並且必須在沒有傳出邊的節點上結束。 如果沒有邊緣出現兩次,則路徑是非循環的

我的圖表(經驗數據集)只有20-160個節點,但是,其中一些節點有很多周期,因此會有非常多的路徑,我的天真方法對某些圖形來說根本不夠快我有。

我目前正在做的是使用遞歸函數沿着所有可能的邊緣“下降”,同時跟蹤我已訪問過的節點(並避免它們)。 到目前為止,我用的最快的解決方案是用C ++編寫的,並在遞歸函數中使用std :: bitset參數來跟蹤已訪問的節點(訪問節點由位1標記)。 該程序在1-2分鍾內在樣本數據集上運行(取決於計算機速度)。 對於其他數據集,運行需要一天以上,或者顯然要長得多。

樣本數據集: http//pastie.org/1763781 (每行是邊對)

樣本數據集的解決方案(第一個數字是我開始的節點,第二個數字是從該節點開始的路徑計數,最后一個數字是總路徑計數): http//pastie.org/1763790

如果您對復雜度較高的算法有所了解,請與我們聯系。 我也對近似解決方案感興趣(用蒙特卡羅方法估算路徑的數量)。 最后,我還想測量平均路徑長度。

編輯:也在相同標題下的MathOverflow上發布,因為它可能更相關。 希望這不違反規則。 無法鏈接,因為網站不允許超過2個鏈接...

這似乎是#P-complete。 參見http://www.maths.uq.edu.au/~kroese/ps/robkro_rev.pdf )。 該鏈接具有近似值

如果您可以放寬簡單路徑要求,則可以使用Floyd-Warshall的修改版本或圖形取冪來有效地計算路徑數量。 請參見圖表上的所有對所有路徑

正如spinning_plate所提到的,這個問題是#P-complete所以開始尋找你的aproximations :)。 我非常喜歡這個問題的#P-completeness證明,所以我認為分享它會很好:

設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,將給我們一個線性系統(帶有Vandermonde矩陣),允許我們找到p_1,...,p_n。

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

因此,找到圖中路徑的數量與查找特定長度的路徑數一樣困難。 特別地,p_n是哈密爾頓路徑的數量(從s開始),這是一個真正的#P-complete問題。


我沒有做過數學運算我也猜測類似的過程應該能夠證明只計算平均長度也很難。


注意:大多數時候會討論這個問題,路徑從單個邊緣開始並在任何地方停止。 這與你的問題相反,但你只需要反轉所有的邊緣就可以了。

問題陳述的重要性

目前還不清楚計算的是什么。

  1. 起始節點是否設置了至少有一個輸出邊緣的所有節點,或者是否存在特定的起始節點標准?
  2. 結束節點是否設置了出局邊緣為零的所有節點的集合,或者至少有一個入局邊緣的任何節點是否可能是結束節點?

定義您的問題,以便沒有歧義。

估計

當設計用於隨機構造的有向圖時,估計可以減少數量級,並且該圖在其構造中具有非常統計上的偏斜或系統性。 這是所有估計過程的典型特征,但由於它們具有指數模式復雜性潛力,因此在圖中尤其明顯。

兩個優化點

由於在特定位偏移處測試位的指令集機制,std :: bitset模型將比大多數處理器體系結構的bool值慢。 當內存占用而非速度是關鍵因素時,bitset更有用。

消除案件或減少通過扣除很重要。 例如,如果存在僅有一個輸出邊緣的節點,則可以計算沒有它的路徑數量,並將子圖形中的路徑數量添加到它所指向的節點的路徑數量。

訴諸集群

可以通過根據起始節點分發在集群上執行該問題。 有些問題只需要超級計算。 如果您有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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM