有向循环图中两个节点之间的路径数量

5
我想在一个有向图中寻找从顶点1到顶点n的路径数。这个图包含循环。如果从顶点1到顶点n的任何一条路径都有一个循环,那么结果应该是“INFINITE PATHS”,否则返回路径数量。以下是我的尝试。
1) 我修改了DFS算法,以1号节点开始,所有节点最初都是白色的。 2) 如果访问了灰色节点,则表示存在一个循环。 3) 使用一个记录所访问的顶点的路径数组来回溯循环中的节点。 4) 对于循环中的每个节点,使用未修改的DFS搜索从该节点到第n个顶点。 5) 如果DFS从任何一个节点成功完成,则在顶点1和顶点n之间的路径中存在一个循环,因此返回,否则算法继续计算路径数。
C ++实现
#include <stdio.h>
#include <malloc.h>
#include <vector>
#include <algorithm>

#define WHITE 0
#define GRAY 1
#define BLACK 2

using namespace std;
typedef struct vertexstruct{
   int color;
   vector<int> edgelist;
}vertex;

int ordinarydfs(int v,vertex *vertices,int numvertices){
    if(v == numvertices){
        return 1;       
    }

    if(vertices[v].color == WHITE){
         vertices[v].color = GRAY;
         for(int i=0;i<vertices[v].edgelist.size();i++){
             int x = ordinarydfs(vertices[v].edgelist[i],vertices,numvertices);
             if(x ==1) return 1;
         }
         vertices[v].color = BLACK; 
     }
     return 0;
}

int checkcycle(int v,vector<int>path,vertex *vertices,int numvertices){
    vector<int>::iterator it;
    vector<int>::iterator x;
    it = find (path.begin(), path.end(), v);
    for(x =it;x<path.end();x++)
        vertices[*x].color = WHITE;
    for(x =it;x<path.end();x++){
        if(ordinarydfs(*x,vertices,numvertices))
            return 1;   
    }
    for(x =it;x<path.end();x++)
        vertices[*x].color = GRAY;

    return 0;

}

long dfs(int v,vertex *vertices,int numvertices,vector<int> path){
    long paths = 0;
    if(v == numvertices){
            return 1;       
    }
    if(vertices[v].color == GRAY) {
         if(checkcycle(v,path,vertices,numvertices)) return -1;
    }   
    if(vertices[v].color == WHITE){
        vertices[v].color = GRAY;
        path.push_back(v);
        for(int i=0;i<vertices[v].edgelist.size();i++){     
            long x = dfs(vertices[v].edgelist[i],vertices,numvertices,path);
            vertices[vertices[v].edgelist[i]].color = WHITE;
            if(x == -1) return -1;
            paths += x;
        }
        vertices[v].color = BLACK;  
    }
    return paths % 1000000000;
}

void numpaths(int numvertices,int numedges,vertex *vertices){
    vector<int> path;
    long numpaths = 0;
    numpaths = dfs(1,vertices,numvertices,path);
    if(numpaths == -1)
        printf("INFINITE PATHS\n");
    else
        printf("%ld\n",numpaths);
}



int main(){
    int edgecount=0;
    int  numverts,numedges;
    fscanf(stdin,"%d %d",&numverts,&numedges);
   vertex verts[numverts+1];
   for(int i =1;i<=numverts;i++)
       verts[i].color = WHITE;
   edgecount = 0; 
   int a,b;
   while(edgecount < numedges){
       scanf("%d %d",&a,&b);
       verts[a].edgelist.push_back(b);
       edgecount++;
       }
   numpaths(numverts,numedges,verts);
}

对于大型图形,该算法速度太慢了。有没有正确的方法来解决这个问题?分享你的想法。谢谢。

4个回答

3
另一种完全不同的方法是将您的图表示为邻接矩阵A[i][j] = 1,如果(i,j)是一条边,则为0。然后,A^i[s][t]计算从s到t的长度为i的路径数(可以通过归纳证明这一点。想想A^2代表什么)。对于幂1..|V|,求和A[s][t],您就拥有了所有可能的长度不超过|V|的路径。要检查是否存在循环,请查看(通过再次乘以矩阵)是否存在长度为|V|+1,...,2|V|的路径。
这有帮助吗?

矩阵乘法的时间复杂度为O(n^3),在稀疏图上速度相当慢。如果使用朴素指数算法,则时间复杂度为O(n^4)。然而,这是一个非常巧妙的技巧。 - tom
1
@tom:你说得对,虽然矩阵乘法在实践中是O(n^2.8),但通过使用Strassen算法,或者更高级的算法,在渐进意义下这个复杂度达到了 O(n^2.3..)。然而,我不确定原始算法是否有效,即使它有效,它的复杂度至少为O(|V|*||V|+E|(仅针对“从每个循环中的每个节点进行DFS”部分,我认为我掌握了)。此外,矩阵乘法具有更高的缓存效率-所以我会在实践中使用它。 - Guy Adini
1
如果循环检测高效完成(请参见我的答案),则DFS实际上是O(|V| + |E|)。如果合并重复边,则|E|最多为|V|^2,因此最坏情况的复杂度为O(|V|^2)。在稀疏图上,它将更快。 - tom

2
您没有使用任何缓存,因此将会多次调用dfs()ordinarydfs()。用于检查循环的path向量是冗余的,因为您可以通过其颜色判断顶点是否在当前路径中。也没有必要特别检查循环是否连接到最后一个顶点,因为您已经在计算有多少路径通向最后一个顶点。
我将整个BFS放在一个方法中,并重写了大部分代码:
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

class Vertex;

const int WHITE = 0, GRAY = 1, BLACK = 2;
// WHITE: unseen, GRAY: in the current path, BLACK: finished (num paths known)
const int CYCLE = -1;

class Vertex
{
public:
    long long paths;
    int color;
    bool hasLoop;
    vector<Vertex *> nbrs;

    Vertex();
    long long countPaths();
};

int main()
{
    int numverts, numedges;
    Vertex** verts; // dynamically-allocated array of Vertex*
    scanf("%d %d", &numverts, &numedges);

    verts = new Vertex*[numverts+1];
    for (int i = 0; i < numverts + 1; i++) verts[i] = new Vertex();

    for (int i = 0; i < numedges; i++)
    {
        int a, b;
        scanf("%d %d", &a, &b);
        verts[a]->nbrs.push_back(verts[b]);
    }

    verts[numverts]->paths = 1; // the base case

    long long numPaths = verts[1]->countPaths();

    // free dynamic memory, set pointers to NULL to prevent accidents
    for (int i = 0; i < numverts; i++) { delete verts[i]; verts[i] = NULL; }
    delete[] verts; verts = NULL;

    if (numPaths == CYCLE) printf("INFINITE PATHS\n");
    else printf("%lld\n", numPaths);

    return 0;
}


Vertex::Vertex()
{
    paths = 0;
    color = WHITE;
    hasLoop = false;
}

long long Vertex::countPaths()
{
    // sets this->paths to the number of paths, or CYCLE (-1) for cycles
    // returns this->paths

    if (color == BLACK)
    {
        // paths already calculated, no need to do anything
    }
    else if (color == GRAY)
    {
        // detected a loop
        hasLoop = true;
    }
    else if (color == WHITE)
    {
        // recursively find all the paths from the neighbours
        color = GRAY;
        for (unsigned int i = 0; i < nbrs.size(); i++)
        {
            nbrs[i]->countPaths();
            if (nbrs[i]->paths == CYCLE)
            {
                // found cycle, no point continuing
                paths = CYCLE;
                break;
            }
            paths += nbrs[i]->paths;
        }
        color = BLACK;

        // check if some other node found a cycle to 'this'
        if (hasLoop && paths > 0) paths = CYCLE;
    }

    return paths;
}

这看起来不错。但有一件事我不确定:如果循环不包含一个从中可以到达目标的顶点,那么它并不意味着路径数量未定义。这个问题如何解决? - Guy Adini
如果(hasLoop && paths > 0),则if (hasLoop && paths > 0) paths = CYCLE;只有在找到一个循环且至少有一条路径时才会使其未定义。 - tom
很棒。我喜欢你的答案比我的好 :) - Guy Adini
看起来很酷,但是你如何确定通过跟随路径是否已经到达了第n个节点。 - vgeta
@Gopikanna,你能否给我一个能够使其出错的测试用例,以便我可以进行调试吗? - tom

1
我们可以通过以下步骤解决这个问题:
  1. 计算图的强连通分量(SCC)。
  2. 将SCC中的所有节点合并为一个节点并标记它们。最终结果是我们得到了一个DAG,即SCC的G'。其中一些SCC可能只包含一个节点。
  3. 对G'进行拓扑排序。
  4. 计算从目标节点到源节点的路径数(使用此处建议的方法)。在使用该方法时,将表示SCC的节点视为特殊情况,并将其标记为具有无限路径(MAX_LONG)。

(如果需要,我将尝试稍后发布此实现)


0

我尝试过这个方法,速度很快,但我不确定它是否正确。当你到达一个黑色节点时,这意味着你已经计算出了从该节点的路径,因此我在节点的结构中存储了路径数量并使用它。


#include <stdio.h>
#include <malloc.h>
#include <vector>
#include <algorithm>

#define WHITE 0
#define GRAY  1
#define BLACK 2

using namespace std; 
typedef struct vertexstruct{
    int color;
    vector<int> edgelist;
    long paths;
}vertex;

int cyclenode;
int throughend;
long dfs(int v,vertex *vertices,int numvertices){
    long paths = 0;
if(vertices[v].color == BLACK) return vertices[v].paths;
if(vertices[v].color == GRAY) {
    if(cyclenode == 0)cyclenode = v;
}   
if(vertices[v].color == WHITE){
    vertices[v].color = GRAY;
    for(int i=0;i<vertices[v].edgelist.size();i++){     
        long x = dfs(vertices[v].edgelist[i],vertices,numvertices);
        if(cyclenode) vertices[v].cycle=1;
        if(x == -1) return -1;
        paths += x;
    }
    if(cyclenode && paths >0) return -1;
    if(v == numvertices){
                if(cyclenode) {
                        return -1;
                }
        throughend = 0;
        vertices[v].paths = 1;      
                return 1;
        }
    if(cyclenode ==v && throughend == 0) cyclenode = 0;
    vertices[v].color = BLACK;
    vertices[v].paths = paths % 1000000000; 
}

return paths % 1000000000;
}

void numpaths(int numvertices,int numedges,vertex *vertices){
        long numpaths = 0;
        cyclenode = 0;
                throughend =0;
        numpaths = dfs(1,vertices,numvertices);
        if(numpaths == -1)
            printf("INFINITE PATHS\n");
        else
            printf("%ld\n",numpaths);
}



int main(){
        int edgecount=0;
        int  numverts,numedges;
        fscanf(stdin,"%d %d",&numverts,&numedges);
    vertex verts[numverts+1];
    for(int i =1;i<=numverts;i++){
        verts[i].color = WHITE;
        verts[i].paths = 0;
    }
    edgecount = 0; 
        int a,b;
    while(edgecount < numedges){
                scanf("%d %d",&a,&b);
        verts[a].edgelist.push_back(b);
                edgecount++;
        }
    numpaths(numverts,numedges,verts);
}

循环检测逻辑中存在一个错误。一次只能检测到一个循环,如果路径上有两个循环,算法可能会出错。请考虑输入5 6 1 2 2 1 1 3 3 4 4 3 1 5。预期输出应该是INFINITE PATHS,但是你的程序输出的是1 - tom
@tom,在你的情况下,答案是1,因为只有一条路径。 - vgeta
我的意思是1和6之间只有一条路径,即1->5->6。2、3中的循环和3、4中的循环并不重要。你觉得呢? - vgeta
还有一个从1到2的循环,所以你可以得到1->2->1->2->1->5 - tom
@tom,是的,你说得对。我已经更正了它(添加了if(cyclenode == 0)cyclenode = v)。现在它会给出无限路径。 - vgeta
显示剩余3条评论

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