Algorithm 计算有向图上非循环路径数的快速算法

Algorithm 计算有向图上非循环路径数的快速算法,algorithm,optimization,graph,complexity-theory,directed-graph,Algorithm,Optimization,Graph,Complexity Theory,Directed Graph,简而言之,我需要一个快速算法来计算一个简单有向图中有多少条非循环路径 所谓简单图,我指的是没有自循环或多条边的图。 路径可以从任何节点开始,并且必须在没有传出边的节点上结束。如果路径中没有两次出现边,则该路径是非循环的 我的图(经验数据集)只有20-160个节点,但是,其中一些节点中有许多循环,因此将有大量的路径,而我的简单方法对于我的一些图来说还不够快 我目前正在做的是使用递归函数沿所有可能的边“递减”,同时跟踪我已经访问过的节点(并避开它们)。到目前为止,我所用的最快的解决方案是用C++编写

简而言之,我需要一个快速算法来计算一个简单有向图中有多少条非循环路径

所谓简单图,我指的是没有自循环或多条边的图。 路径可以从任何节点开始,并且必须在没有传出边的节点上结束。如果路径中没有两次出现边,则该路径是非循环的

我的图(经验数据集)只有20-160个节点,但是,其中一些节点中有许多循环,因此将有大量的路径,而我的简单方法对于我的一些图来说还不够快

我目前正在做的是使用递归函数沿所有可能的边“递减”,同时跟踪我已经访问过的节点(并避开它们)。到目前为止,我所用的最快的解决方案是用C++编写的,并且在递归函数中使用STD::BITSSET参数来跟踪哪些节点已经被访问过(访问节点用位1标记)。该程序在样本数据集上运行1-2分钟(取决于计算机速度)。对于其他数据集,运行需要一天以上的时间,或者显然要长得多

示例数据集: (每条线是一对边)

示例数据集的解决方案(第一个数字是我从中开始的节点,第二个数字是从该节点开始的路径计数,最后一个数字是总路径计数):

请让我知道,如果你有一个更复杂的算法的想法。我还对近似解感兴趣(用蒙特卡罗方法估计路径数)。最后,我还要测量平均路径长度

编辑:也以相同的标题发布在MathOverflow上,因为它可能与MathOverflow更相关。希望这不违反规定。无法链接,因为站点不允许超过2个链接…

这似乎是“p-完成”。(参考)。链接有一个近似值


如果您可以放宽简单路径的要求,那么您也可以使用经过修改的Floyd Warshall版本或图形求幂来有效地计算路径数。如Spining_plate所述,这个问题是完全的,所以开始寻找你的答案:)。我真的很喜欢这个问题的#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-完全问题


我还没有做过数学计算,我猜类似的过程应该能够证明仅仅计算平均长度也是很困难的



注意:大多数情况下,讨论此问题时,路径从一条边开始,并在任何位置停止。这与你的问题正好相反,但你应该把所有的边都颠倒过来,使它们相等。

问题陈述的重要性

目前尚不清楚统计的是什么

  • 起始节点是否设置为至少有一条传出边的所有节点,或者是否有特定的起始节点条件
  • “结束节点集”是指有零个传出边的所有节点集,还是至少有一个传入边的任何节点都可以是可能的结束节点
  • 定义您的问题,以便没有歧义

    估算

    当为随机构造的有向图设计时,估计可能会偏离数量级,并且该图的构造在统计上非常倾斜或系统化。这是所有估计过程的典型特征,但在图中尤其明显,因为它们具有指数模式复杂性潜力

    两个优化点

    对于大多数处理器体系结构,std::位集模型将比bool值慢,因为指令集机制是在特定位偏移量下测试位。当内存占用而不是速度是关键因素时,位集更有用

    消除案例或通过扣减减少成本非常重要。例如,如果存在只有一条传出边的节点,则可以计算没有传出边的路径数,并将其指向的节点的路径数添加到子图中的路径数

    诉诸集群

    该问题可以通过按起始节点分布在集群上执行。有些问题只需要超级计算。如果有1000000个起始节点和10个处理器,则可以在每个处理器上放置100000个起始节点案例。上述案例消除和减少应在分发案例之前完成

    典型的深度优先递归及其优化方法

    这是一个小程序,它提供了一个基本的深度优先,从任何节点到任何节点的非循环遍历,可以更改、放置在循环中或分布。如果已知最大数据集大小,则可以使用一个模板将列表放入静态本机数组中,该模板的大小作为一个参数,从而减少迭代和索引时间

    #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;
    }
    
    #包括
    #包括
    类定向图{
    私人:
    内米诺德斯;
    标准::列表*mnpEdges;
    bool*mpVisitedFlags;
    私人:
    void initAlreadyVisited(){
    对于(int i=0;i