在有向图上的广度优先搜索期间的边缘分类

时间:2015-04-14 15:20:13

标签: algorithm graph-theory graph-algorithm breadth-first-search directed-graph

在定向图上进行广度优先搜索时,我很难找到正确分类边的方法。

在广度优先或深度优先搜索期间,您可以对满足4个类的边进行分类:

  • TREE
  • BACK
  • CROSS
  • FORWARD

Skiena [1]给出了一个实现。如果沿着从v1到v2的边缘移动,这里是一种在Java中的DFS期间返回类的方法,以供参考。父映射返回当前搜索的父顶点,以及timeOf()方法,即发现顶点的时间。

if ( v1.equals( parents.get( v2 ) ) ) { return EdgeClass.TREE; }
    if ( discovered.contains( v2 ) && !processed.contains( v2 ) ) { return EdgeClass.BACK; }
    if ( processed.contains( v2 ) )
    {
        if ( timeOf( v1 ) < timeOf( v2 ) )
        {
            return EdgeClass.FORWARD;
        }
        else
        {
            return EdgeClass.CROSS;
        }
    }
    return EdgeClass.UNCLASSIFIED;

我的问题是我无法在有向图上进行广度优先搜索。例如:

下图 - 这是一个循环 - 没问题:

A -> B
A -> C
B -> C

将发现来自A,B的BFS,然后是C.边缘eAB和eAC是TREE边缘,并且当最后交叉eBC时,处理并发现B和C,并且该边缘被正确地分类为CROSS。

但是一个普通循环不起作用:

A -> B
B -> C
C -> A

当最后交叉边缘eCA时,处理并发现A.因此,此边缘被错误地标记为CROSS,是否应该是BACK边缘。

两种情况的处理方式确实没有区别,即使两条边有不同的类。

如何在有向图上为BFS实现正确的边缘分类?

[1] http://www.algorist.com/


修改

这里是一个源自@redtuna答案的实现。 我刚刚添加了一个检查,不提取root的父级。 我有JUnits测试,显示它适用于有向和无向图,在循环,直线,分叉,标准示例,单边等情况下....

@Override
public EdgeClass edgeClass( final V from, final V to )
{
    if ( !discovered.contains( to ) ) { return EdgeClass.TREE; }

    int toDepth = depths.get( to );
    int fromDepth = depths.get( from );

    V b = to;
    while ( toDepth > 0 && fromDepth < toDepth )
    {
        b = parents.get( b );
        toDepth = depths.get( b );
    }

    V a = from;
    while ( fromDepth > 0 && toDepth < fromDepth )
    {
        a = parents.get( a );
        fromDepth = depths.get( a );
    }

    if ( a.equals( b ) )
    {
        return EdgeClass.BACK;
    }
    else
    {
        return EdgeClass.CROSS;
    }

}

3 个答案:

答案 0 :(得分:3)

  

如何在a上为BFS实现正确的边缘分类   有向图?

正如您已经建立的那样,第一次看到节点会创建树边缘。正如David Eisenstat在我之前所说的那样,BFS而不是DFS的问题在于,只有基于遍历顺序才能将后边缘与交叉边缘区分开来。

相反,你需要做一些额外的工作来区分它们。正如您所看到的,关键是使用交叉边缘的定义。

最简单(但内存密集)的方法是将每个节点与其前任集合相关联。当您访问节点时,这可以轻松完成。在节点a和b之间找到非树边时,请考虑它们的前任集。如果一个是另一个的适当子集,那么你有一个后沿。否则,它是一个交叉边缘。这直接来自交叉边缘的定义:它是节点之间的边缘,其中既不是树的祖先也不是另一个的后代。

更好的方法是仅关联&#34;深度&#34;每个节点而不是一组的数字。同样,当您访问节点时,这很容易完成。现在,当您在a和b之间找到非树边缘时,从两个节点的较深处开始并向后跟随树边缘,直到您返回到与另一个相同的深度。所以例如假设a更深。然后你重复计算a = parent(a),直到深度(a)=深度(b)。

如果此时a = b,则可以将边缘分类为后边缘,因为根据定义,其中一个节点是树中另一个节点的祖先。否则你可以把它归类为交叉边缘,因为我们知道这两个节点都不是另一个节点的祖先或后代。

伪代码:

  foreach edge(a,b) in BFS order:
    if !b.known then:
      b.known = true
      b.depth = a.depth+1
      edge type is TREE
      continue to next edge
    while (b.depth > a.depth): b=parent(b)
    while (a.depth > b.depth): a=parent(a)
    if a==b then:
      edge type is BACK
    else:
      edge type is CROSS

答案 1 :(得分:2)

这里DFS的关键属性是,给定两个节点u和v,区间[u.discovered,u.processed]是[v.discovered,v.processed]的子区间,当且仅当你是一个v的后代.BFS中的时间没有这个属性;您必须做其他事情,例如,通过BFS生成的树上的DFS计算间隔。然后分类伪代码是1.检查树中的成员资格(树边缘)2。检查头部的间隔是否包含尾部(后边缘)3。检查尾部的间隔是否包含头部(前沿)4。否则,声明一个交叉边缘。

答案 2 :(得分:1)

而不是timeof(),您需要一个其他顶点属性,它包含与根的距离。我们命名为distance

您必须按以下方式处理v顶点:

for (v0 in v.neighbours) {
    if (!v0.discovered) {
        v0.discovered = true; 
        v0.parent = v;
        v0.distance = v.distance + 1;
    }
}
v.processed = true;

在处理顶点v顶点后,您可以为v1的每个边(从v2v)运行以下算法:

if (!v1.discovered) return EdgeClass.BACK;  
else if (!v2.discovered) return EdgeClass.FORWARD; 
else if (v1.distance == v2.distance) return EdgeClass.CROSS;
else if (v1.distance > v2.distance) return EdgeClass.BACK;
else {
    if (v2.parent == v1) return EdgeClass.TREE;
    else return EdgeClass.FORWARD;
}