在有向图上计算无环路径数量的快速算法

10
简而言之,我需要一种快速的算法来计算一个简单有向图中有多少无环路径。
所谓的“简单”图是指没有自环或重边的图。路径可以从任何节点开始,并必须以没有出边的节点结束。如果路径中没有边出现两次,则该路径是“无环”的。
我的图(经验数据集)仅具有20-160个节点,但其中一些具有许多循环,因此将有非常多的路径,我的朴素方法对于其中一些图来说并不快速。
我目前正在使用递归函数沿着所有可能的边“下降”,同时跟踪我已经访问过的节点(并避免它们)。 到目前为止,我最快的解决方案是用C++编写的,并使用递归函数中的std::bitset参数来跟踪哪些节点已经被访问过(已访问的节点由位1标记)。 该程序在样本数据集上运行需要1-2分钟(取决于计算机速度)。 对于其他数据集,它需要运行超过一天甚至更长时间。
样本数据集:http://pastie.org/1763781 (每行都是一个边对)
样本数据集的解决方案(第一个数字是我从哪个节点开始,第二个数字是从该节点开始的路径数,最后一个数字是总路径数): http://pastie.org/1763790 请告诉我是否有更好时间复杂度的算法,我也对近似解(使用蒙特卡罗方法估计路径数量)感兴趣。最终,我还想测量平均路径长度。
编辑:同样的问题已在MathOverflow上发布,因为那里可能更相关。希望这不违反规定。无法链接,因为该网站不允许超过2个链接...
3个回答

9

1
嗨,只是为了澄清---这不是一个近似算法(即具有已知误差界限的算法),而是提供统计估计的随机算法。原始问题询问蒙特卡罗方法,您的参考资料中提供了该方法,因此可能需要强调一下。 - Antti Huima

4

如spinning_plate所述,这个问题是#P完全问题,因此开始寻找你的近似解 : )。我非常喜欢这个问题的#P完全性证明,所以我想分享一下:

设N为图中从起点开始的路径数,p_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

问题陈述的重要性

不清楚正在计算什么。

  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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接