【问题标题】:transitive reduction algorithm: pseudocode?传递约简算法:伪代码?
【发布时间】:2010-12-14 00:57:48
【问题描述】:

我一直在寻找一种算法来对图执行传递缩减,但没有成功。我的算法圣经(Cormen 等人的算法简介)中没有任何内容,虽然我已经看到很多传递闭包伪代码,但我无法找到任何减少的东西。我得到的最接近的是 Volker Turau 的“Algorithmische Graphentheorie”中的一本书(ISBN:978-3-486-59057-9),但不幸的是我无法访问这本书!维基百科没有帮助,谷歌还没有发现任何东西。 :^(

有人知道执行传递缩减的算法吗?

【问题讨论】:

    标签: algorithm graph pseudocode


    【解决方案1】:

    见哈里·许。 “一种寻找有向图的最小等价图的算法。”,ACM 杂志,22(1):11-16,1975 年 1 月。下面的简单三次算法(使用 N x N 路径矩阵)足以满足 DAG,但 Hsu 将其推广到循环图。

    // reflexive reduction
    for (int i = 0; i < N; ++i)
      m[i][i] = false;
    
    // transitive reduction
    for (int j = 0; j < N; ++j)
      for (int i = 0; i < N; ++i)
        if (m[i][j])
          for (int k = 0; k < N; ++k)
            if (m[j][k])
              m[i][k] = false;
    

    【讨论】:

    • (对于 DAG)换句话说:查看每条边 (i,j),如果有理由不参与传递缩减,则将其删除。未移除的边必须在传递归约内。
    • 根据你引用的参考,你应该从路径矩阵开始,而不是邻接矩阵
    • 这不适用于所有情况。在具有边 (A,B)、(B,C)、(C,D) 和 (A,D) 的图中,应删除最后一条边 (A,D)。不是,因为不存在从 A 到 D 的两条边(m[i][j] 和 m[j][k])的组合。
    • @MichaelClerx 非常正确,我的意思是路径矩阵。感谢您指出错误。如果你有一个邻接矩阵,首先应用 Warshal 的算法来传递关闭它。
    【解决方案2】:

    我使用的传递约简算法的基本要点是

    foreach x in graph.vertices foreach y in graph.vertices foreach z in graph.vertices delete edge xz if edges xy and yz exist

    我在同一个脚本中使用的传递闭包算法非常相似,但最后一行是

    
             add edge xz if edges xy and yz OR edge xz exist
    

    【讨论】:

    • 您需要在delete edge...之前添加if (x,z) != (x,y) &amp;&amp; (x,z) != (y,z),以避免在出现循环时错误删除。除此之外,虽然最好有一个更快的线性时间算法,但我喜欢这个答案:又好又简单。
    • 另外,如果图有环,这个算法不会总是产生 minimal 传递约简。例如,在[0,1,2,3,4,5] 上尝试,其中 A 指向所有 A 和 B 的 B(即使它们相同)。它应该产生类似 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 0 的东西,但是运行这个算法(通过我的调整)除了 0 - 之外还会带来 5 -> 2 和 5 -> 4 > ... -> 5 -> 0。在没有我的调整的情况下运行它根本不会产生任何边缘。
    • 我应该说我的代码包括检查你提到的相同边缘,而且我只使用 DAG,所以循环不是问题。
    • 你确定你的传递闭包算法吗?对于该任务,我将使用 Floyd-Warshall 的算法,即foreach y in graph.vertices: foreach x in graph.vertices: foreach z in graph.vertices: add edge xz if edges xy and yz exist OR edge xz exist。注意xy 中的不同顺序。我认为顺序很重要。没有?
    • 正如 cmn 所指出的,该算法确实清除了连接节点的边,这些节点也通过具有两个以上边的路径连接。示例:A -> B -> C -> D; A -> C; A-> D。算法会清除 A -> C,但不会清除 A -> D。
    【解决方案3】:

    根据 Alan Donovan 提供的参考资料,它说您应该使用路径矩阵(如果存在从节点 i 到节点 j 的路径,则为 1)而不是邻接矩阵(仅当存在是从节点 i 到节点 j 的一条边。

    下面的一些示例 python 代码显示了解决方案之间的差异

    def prima(m, title=None):
        """ Prints a matrix to the terminal """
        if title:
            print title
        for row in m:
            print ', '.join([str(x) for x in row])
        print ''
    
    def path(m):
        """ Returns a path matrix """
        p = [list(row) for row in m]
        n = len(p)
        for i in xrange(0, n):
            for j in xrange(0, n):
                if i == j:
                    continue
                if p[j][i]:
                    for k in xrange(0, n):
                        if p[j][k] == 0:
                            p[j][k] = p[i][k]
        return p
    
    def hsu(m):
        """ Transforms a given directed acyclic graph into its minimal equivalent """
        n = len(m)
        for j in xrange(n):
            for i in xrange(n):
                if m[i][j]:
                    for k in xrange(n):
                        if m[j][k]:
                            m[i][k] = 0
    
    m = [   [0, 1, 1, 0, 0],
            [0, 0, 0, 0, 0],
            [0, 0, 0, 1, 1],
            [0, 0, 0, 0, 1],
            [0, 1, 0, 0, 0]]
    
    prima(m, 'Original matrix')
    hsu(m)
    prima(m, 'After Hsu')
    
    p = path(m)
    prima(p, 'Path matrix')
    hsu(p)
    prima(p, 'After Hsu')
    

    输出:

    Adjacency matrix
    0, 1, 1, 0, 0
    0, 0, 0, 0, 0
    0, 0, 0, 1, 1
    0, 0, 0, 0, 1
    0, 1, 0, 0, 0
    
    After Hsu
    0, 1, 1, 0, 0
    0, 0, 0, 0, 0
    0, 0, 0, 1, 0
    0, 0, 0, 0, 1
    0, 1, 0, 0, 0
    
    Path matrix
    0, 1, 1, 1, 1
    0, 0, 0, 0, 0
    0, 1, 0, 1, 1
    0, 1, 0, 0, 1
    0, 1, 0, 0, 0
    
    After Hsu
    0, 0, 1, 0, 0
    0, 0, 0, 0, 0
    0, 0, 0, 1, 0
    0, 0, 0, 0, 1
    0, 1, 0, 0, 0
    

    【讨论】:

    • 我很困惑,因为如果您以正确的顺序删除边缘,您可以通过将算法应用于路径矩阵来直接回到原始(冗余)邻接矩阵。所以基本上你一无所获。请参阅此示例:i.imgur.com/fbt6oK1.png 假设您只从黑色边缘开始,当然您想要消除点状黑色/绿色边缘。因此,您添加红色边缘以获取路径矩阵。然后删除红色边缘,因为它们都可以被算法删除。现在你被卡住了。
    • 使用 m = [[0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0 ]] 作为输入工作正常:)
    • 我认为它可以工作,只要你不是不走运先删除哪些边缘。
    • 试试看,顺序没区别。
    • 好的,对不起,你是对的,我找不到在两个红色边缘之前去除虚线黑色/绿色边缘的任何情况。当我今晚回到家时,我会试着弄清楚为什么会发生这种情况。
    【解决方案4】:

    关于传递约简的Wikipedia article 指向 GraphViz(开源)中的一个实现。不完全是伪代码,但也许可以从某个地方开始?

    LEDA 包括一个transitive reduction algorithm。我已经没有LEDA book的副本了,这个功能可能是在本书出版后添加的。但如果它在那里,那么就会有一个很好的算法描述。

    Google 指出有人建议将 an algorithm 包含在 Boost 中。我没有尝试阅读它,所以可能不正确?

    另外,this 可能值得一看。

    【讨论】:

    • 感谢(迟到了!)您的回复。最后,我给算法书的作者发了一封邮件,请他验证我写的一些伪代码是否正确,他很友好地做到了。
    • 由于代码中没有任何注释,tred source code 几乎无法阅读。
    【解决方案5】:

    “戴眼镜的女孩”的算法忘记了冗余边可能跨越三个边的链。要更正,请计算 Q = R x R+,其中 R+ 是传递闭包,然后从 R 中删除出现在 Q 中的所有边。另请参阅 Wikipedia 文章。

    【讨论】:

    • 你能推荐一些伪代码来做这件事吗?下面发布的传递约简算法将在传递闭包图上运行,因此对于 x-A-B-y 也可以到达的边 x-y,您还将有 x-A-y 和 x-B-y。
    • Q应该代表什么?你用它做什么?
    【解决方案6】:

    伪python中的深度优先算法:

    for vertex0 in vertices:
        done = set()
        for child in vertex0.children:
            df(edges, vertex0, child, done)
    
    df = function(edges, vertex0, child0, done)
        if child0 in done:
            return
        for child in child0.children:
            edge.discard((vertex0, child))
            df(edges, vertex0, child, done)
        done.add(child0)
    

    该算法是次优的,但处理的是先前解决方案的多边跨度问题。结果与 graphviz 中的 tred 产生的结果非常相似。

    【讨论】:

      【解决方案7】:

      移植到 java / jgrapht,此页面上的 python 示例来自@Michael Clerx:

      import java.util.ArrayList;
      import java.util.List;
      import java.util.Set;
      
      import org.jgrapht.DirectedGraph;
      
      public class TransitiveReduction<V, E> {
      
          final private List<V> vertices;
          final private int [][] pathMatrix;
          
          private final DirectedGraph<V, E> graph;
          
          public TransitiveReduction(DirectedGraph<V, E> graph) {
              super();
              this.graph = graph;
              this.vertices = new ArrayList<V>(graph.vertexSet());
              int n = vertices.size();
              int[][] original = new int[n][n];
      
              // initialize matrix with zeros
              // --> 0 is the default value for int arrays
              
              // initialize matrix with edges
              Set<E> edges = graph.edgeSet();
              for (E edge : edges) {
                  V v1 = graph.getEdgeSource(edge);
                  V v2 = graph.getEdgeTarget(edge);
      
                  int v_1 = vertices.indexOf(v1);
                  int v_2 = vertices.indexOf(v2);
      
                  original[v_1][v_2] = 1;
              }
              
              this.pathMatrix = original;
              transformToPathMatrix(this.pathMatrix);
          }
          
          // (package visible for unit testing)
          static void transformToPathMatrix(int[][] matrix) {
              // compute path matrix 
              for (int i = 0; i < matrix.length; i++) {
                  for (int j = 0; j < matrix.length; j++) { 
                      if (i == j) {
                          continue;
                      }
                      if (matrix[j][i] > 0 ){
                          for (int k = 0; k < matrix.length; k++) {
                              if (matrix[j][k] == 0) {
                                  matrix[j][k] = matrix[i][k];
                              }
                          }
                      }
                  }
              }
          }
      
          // (package visible for unit testing)
          static void transitiveReduction(int[][] pathMatrix) {
              // transitively reduce
              for (int j = 0; j < pathMatrix.length; j++) { 
                  for (int i = 0; i < pathMatrix.length; i++) {
                      if (pathMatrix[i][j] > 0){
                          for (int k = 0; k < pathMatrix.length; k++) {
                              if (pathMatrix[j][k] > 0) {
                                  pathMatrix[i][k] = 0;
                              }
                          }
                      }
                  }
              }
          }
      
          public void reduce() {
              
              int n = pathMatrix.length;
              int[][] transitivelyReducedMatrix = new int[n][n];
              System.arraycopy(pathMatrix, 0, transitivelyReducedMatrix, 0, pathMatrix.length);
              transitiveReduction(transitivelyReducedMatrix);
              
              for (int i = 0; i <n; i++) {
                  for (int j = 0; j < n; j++) { 
                      if (transitivelyReducedMatrix[i][j] == 0) {
                          // System.out.println("removing "+vertices.get(i)+" -> "+vertices.get(j));
                          graph.removeEdge(graph.getEdge(vertices.get(i), vertices.get(j)));
                      }
                  }
              }
          }
      }
      

      单元测试:

      import java.util.Arrays;
      
      import org.junit.Assert;
      import org.junit.Test;
      
      public class TransitiveReductionTest {
      
          @Test
          public void test() {
      
              int[][] matrix = new int[][] {
                  {0, 1, 1, 0, 0},
                  {0, 0, 0, 0, 0},
                  {0, 0, 0, 1, 1},
                  {0, 0, 0, 0, 1},
                  {0, 1, 0, 0, 0}
              };
              
              int[][] expected_path_matrix = new int[][] {
                  {0, 1, 1, 1, 1},
                  {0, 0, 0, 0, 0},
                  {0, 1, 0, 1, 1},
                  {0, 1, 0, 0, 1},
                  {0, 1, 0, 0, 0}
              };
              
              int[][] expected_transitively_reduced_matrix = new int[][] {
                  {0, 0, 1, 0, 0},
                  {0, 0, 0, 0, 0},
                  {0, 0, 0, 1, 0},
                  {0, 0, 0, 0, 1},
                  {0, 1, 0, 0, 0}
              };
              
              System.out.println(Arrays.deepToString(matrix) + " original matrix");
      
              int n = matrix.length;
      
              // calc path matrix
              int[][] path_matrix = new int[n][n];
              {
                  System.arraycopy(matrix, 0, path_matrix, 0, matrix.length);
                  
                  TransitiveReduction.transformToPathMatrix(path_matrix);
                  System.out.println(Arrays.deepToString(path_matrix) + " path matrix");
                  Assert.assertArrayEquals(expected_path_matrix, path_matrix);
              }
      
              // calc transitive reduction
              {
                  int[][] transitively_reduced_matrix = new int[n][n];
                  System.arraycopy(path_matrix, 0, transitively_reduced_matrix, 0, matrix.length);
                  
                  TransitiveReduction.transitiveReduction(transitively_reduced_matrix);
                  System.out.println(Arrays.deepToString(transitively_reduced_matrix) + " transitive reduction");
                  Assert.assertArrayEquals(expected_transitively_reduced_matrix, transitively_reduced_matrix);
              }
          }
      }
      

      测试输出

      [[0, 1, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 1], [0, 0, 0, 0, 1], [0, 1, 0, 0, 0]] original matrix
      [[0, 1, 1, 1, 1], [0, 0, 0, 0, 0], [0, 1, 0, 1, 1], [0, 1, 0, 0, 1], [0, 1, 0, 0, 0]] path matrix
      [[0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1], [0, 1, 0, 0, 0]] transitive reduction
      

      【讨论】:

      • 供您参考,已提交、接受并合并到 jgrapht 中的包含此代码的重新设计版本的拉取请求。 github.com/jgrapht/jgrapht/commit/…
      • 仅供参考,如果图形包含循环,JGraphT 中的算法将不起作用,请参阅issue #667。你能检查一下它有什么问题吗?
      猜你喜欢
      • 2019-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-16
      • 2014-08-17
      • 2021-12-21
      相关资源
      最近更新 更多