10

要するに、単純な有向グラフにいくつの非周期的パスがあるかを数えるための高速アルゴリズムが必要です。

単純なグラフとは、自己ループや多重辺のないグラフを意味します。パスは任意のノードから開始でき、出力エッジのないノードで終了する必要があります。パス内でエッジが2回発生しない場合、パスは非循環です。

私のグラフ(経験的データセット)には20〜160のノードしかありませんが、一部のノードには多くのサイクルがあるため、パスの数が非常に多くなり、私の素朴なアプローチは、一部のグラフに対して十分に高速ではありません。私は持っています。

私が現在行っているのは、再帰関数を使用してすべての可能なエッジに沿って「降順」であり、既にアクセスしたノードを追跡します(そしてそれらを回避します)。これまでの最速のソリューションはC++で記述されており、再帰関数でstd :: bitset引数を使用して、既にアクセスされたノードを追跡します(アクセスされたノードはビット1でマークされます)。このプログラムは、サンプルデータセットで1〜2分で実行されます(コンピューターの速度によって異なります)。他のデータセットでは、実行に1日以上、または明らかにはるかに長い時間がかかります。

サンプルデータセット:http://pastie.org/1763781 (各行はエッジペアです)

サンプルデータセットのソリューション(最初の数字は私が開始しているノード、2番目の数字はそのノードから始まるパスカウント、最後の数字は合計パスカウント):http: //pastie.org/1763790

より複雑なアルゴリズムについてのアイデアがあれば教えてください。近似解にも興味があります(モンテカルロアプローチでパスの数を推定します)。最終的には、平均パス長も測定したいと思います。

編集:MathOverflowにも同じタイトルで投稿されています。これが規則に違反しないことを願っています。サイトは2つ以上のリンクを許可しないため、リンクできません...

4

3 に答える 3

9

これは#P-completeです。(http://www.maths.uq.edu.au/~kroese/ps/robkro_rev.pdfを参照)。リンクには概算があります

単純なパス要件を緩和できる場合は、Floyd-Warshallの修正バージョンまたはグラフのべき乗を使用して、パスの数を効率的にカウントできます。グラフ上のすべてのペアのすべてのパスを表示

于 2011-04-06T16:18:19.320 に答える
4

spining_plateで述べたように、この問題は#P-completeであるため、近似値を探し始めます:)。私はこの問題の#P-完全性の証明が本当に好きなので、それを共有するのがいいと思います:

グラフ内のパスの数( sから開始)をNとし、長さkのパスの数をp_kとします。我々は持っています:

N = p_1 + p_2 + ... + p_n

次に、すべてのエッジを平行なエッジのペアに変更して、2番目のグラフを作成します。長さkの各パスには、k^2個のパスがあります。

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

このプロセスを繰り返しますが、 2ではなくiエッジを使用して、nを上げると、線形システム(ファンデルモンド行列を使用)が得られ、p_1、...、p_nを見つけることができます。

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

したがって、グラフ内のパスの数を見つけることは、特定の長さのパスの数を見つけることと同じくらい困難です。特に、p_nはハミルトンパス(sから始まる)の数であり、善意の#P-完全問題です。


私は数学をしていません。また、同様のプロセスで、平均の長さを計算するだけでも難しいことを証明できるはずです。


注:ほとんどの場合、この問題は、パスが単一のエッジから始まり、どこでも停止することについて説明されています。これはあなたの問題とは反対ですが、すべてのエッジを逆にするだけで同等になるはずです。

于 2011-04-06T17:49:08.553 に答える
0

問題ステートメントの重要性

何がカウントされているのかは不明です。

  1. 開始ノードは、少なくとも1つの出力エッジがあるすべてのノードを設定しますか、それとも特定の開始ノード基準がありますか?
  2. 終了ノードセットは、出力エッジがゼロのすべてのノードのセットですか、それとも少なくとも1つの入力エッジがあるノードが終了ノードになる可能性がありますか?

あいまいさがないように問題を定義します。

推定

ランダムに作成された有向グラフ用に設計された場合、推定は桁違いにずれることがあり、グラフの作成は統計的に非常に歪んでいるか、体系的です。これはすべての推定プロセスに典型的ですが、指数関数的なパターンの複雑さの可能性があるため、グラフで特に顕著です。

2つの最適化ポイント

std :: bitsetモデルは、特定のビットオフセットでビットをテストする命令セットの仕組みのため、ほとんどのプロセッサアーキテクチャのブール値よりも遅くなります。ビットセットは、速度ではなくメモリフットプリントが重要な要素である場合に、より便利です。

ケースを排除するか、控除によって削減することが重要です。たとえば、出力エッジが1つしかないノードがある場合、それがないパスの数を計算し、サブグラフのパスの数に、それが指すノードからのパスの数を追加できます。

クラスターに頼る

問題は、開始ノードに従って分散することにより、クラスター上で実行できます。いくつかの問題は単にスーパーコンピューティングを必要とします。1,000,000個の開始ノードと10個のプロセッサーがある場合、各プロセッサーに100,000個の開始ノードケースを配置できます。上記のケースの削除と削減は、ケースを配布する前に行う必要があります。

典型的な深さ優先再帰とそれを最適化する方法

これは、基本的な深さ優先、任意のノードから任意のノードへの非周期的トラバーサルを提供する小さなプログラムです。これは、変更、ループへの配置、または分散が可能です。最大データセットサイズがわかっている場合は、サイズを1つのパラメーターとして持つテンプレートを使用して、リストを静的ネイティブ配列に配置できます。これにより、反復とインデックス作成の時間が短縮されます。

#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;
}
于 2017-02-18T08:29:37.237 に答える