简而言之,我需要一个快速算法来计算简单有向图中有多少非循环路径。
通过简单图表我指的是没有自循环或多个边缘的图形。 路径可以从任何节点开始,并且必须在没有传出边缘的节点上结束。如果没有边缘出现两次,则路径为非循环。
一些我的图表(经验数据集)只有20-160个节点,但是,其中一些节点有很多周期,因此会有很多路径,我的天真方法根本不够快我的图表。我目前正在做的是使用递归函数沿着所有可能的边缘“下降”,同时跟踪我已经访问过的节点(并避免它们)。到目前为止,我用的最快的解决方案是用C ++编写的,并在递归函数中使用std :: bitset参数来跟踪已访问的节点(访问节点由位1标记)。该程序在1-2分钟内在样本数据集上运行(取决于计算机速度)。对于其他数据集,运行需要一天以上,或者显然更长。
示例数据集:http://pastie.org/1763781 (每一行都是边对)
示例数据集的解决方案(第一个数字是我开始的节点,第二个数字是从该节点开始的路径计数,最后一个数字是总路径计数): http://pastie.org/1763790
如果您对复杂度较高的算法有所了解,请与我们联系。我也对近似解决方案感兴趣(用蒙特卡罗方法估算路径的数量)。最后,我还想测量平均路径长度。
编辑:也在同一标题下发布在MathOverflow上,因为它可能更相关。希望这不违反规则。无法链接,因为网站不允许超过2个链接...
答案 0 :(得分:9)
这似乎是#P-complete。 (参考http://www.maths.uq.edu.au/~kroese/ps/robkro_rev.pdf)。该链接具有近似值
如果您可以放宽简单路径要求,则可以使用Floyd-Warshall的修改版本或图形取幂来有效地计算路径数量。见All pairs all paths on a graph
答案 1 :(得分:4)
正如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。 / p>
N_i = p_1*i + p_2*(i^2) + ...
因此,找到图表中的路径数量与查找特定长度的路径数量一样困难。特别是,p_n是汉密尔顿路径的数量(从 s 开始),这是一个真正的#P-complete问题。
我没有做过数学运算我也猜测类似的过程应该能够证明只计算平均长度也很难。
注意:大多数情况下会讨论这个问题,路径从单个边缘开始并在任何地方停止。这与你的问题相反,但你只需要反转所有边缘就可以了。
答案 2 :(得分:0)
问题陈述的重要性
目前还不清楚是在计算什么。
定义您的问题,以免产生歧义。
<强>估计强>
当设计用于随机构造的有向图时,估计可以减少数量级,并且该图在其构造中具有非常统计上的偏斜或系统性。这是所有估算过程的典型特征,但由于它们具有指数模式复杂性潜力,因此在图表中尤为明显。
两个优化点
对于大多数处理器体系结构,std :: bitset模型将比bool值慢,因为在特定位偏移处测试位的指令集机制。当内存占用而非速度是关键因素时,bitset更有用。
消除案件或减少通过扣除很重要。例如,如果存在仅有一个输出边缘的节点,则可以计算没有它的路径数量,并将子图形中路径的数量添加到它所指向的节点的路径数量。 / p>
诉诸集群
可以通过根据起始节点进行分发来在群集上执行该问题。有些问题只需要超级计算。如果您有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;
}