【问题标题】:Edge classification during Breadth-first search on a directed graph有向图上广度优先搜索期间的边分类
【发布时间】:2015-04-14 15:20:13
【问题描述】:

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

在广度优先或深度优先搜索期间,您可以将遇到的边缘分为 4 类:

  • 返回
  • 交叉
  • 前进

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 开始 BFS 会发现 B,然后是 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 答案派生的实现。 我刚刚添加了一个检查以不获取根的父级。 我有 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;
    }

}

【问题讨论】:

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


    【解决方案1】:

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

    正如您已经建立的,第一次看到一个节点会创建一个树边。正如 David Eisenstat 在我之前所说,使用 BFS 而不是 DFS 的问题在于,仅基于遍历顺序无法区分后边缘和交叉边缘。

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

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

    更好的方法是只将一个“深度”数字与每个节点而不是一组相关联。同样,当您访问节点时,这很容易完成。现在,当您在 a 和 b 之间找到非树边时,从两个节点中较深的节点开始,然后沿着树边向后,直到回到与另一个相同的深度。例如,假设 a 更深。然后你重复计算 a=parent(a) 直到 depth(a)=depth(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
    

    【讨论】:

    • 我成功实施并测试了这个解决方案。无论图形的有向/无向性如何,它都可以工作。我做了一些小的更改,我将在问题下方发布。谢谢!
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您需要另一个顶点属性来代替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 顶点后,您可以对v 的每条边(从v1v2)运行以下算法:

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

      【讨论】:

      • 嘿,谢谢 Zsolt!我一直在努力实现我收集的 3 个答案。对于你的,我想我有一个反例。考虑一个菱形:A-B、A-C、B-D、C-D BFS 将遍历 A、B、C 和 D。最后交叉的边是 eCD,并且被错误分类。此时C被发现并处理,D被发现且未处理。它们的距离不同,并且 dD > dC。因此它被错误地归类为 FORWARD 边缘,但它是 CROSS 边缘。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多