алгоритм транзитивной редукции: псевдокод?

Я искал алгоритм для выполнения транзитивной редукции на графике, но безуспешно. В моей библии алгоритмов («Введение в алгоритмы» Кормена и др.) Ничего нет, и хотя я видел много псевдокода транзитивного замыкания, я не смог найти ничего для сокращения. Самое близкое, что у меня есть, это то, что он есть в "Algorithmische Graphentheorie" Фолькера Турау (ISBN: 978-3-486-59057-9), но, к сожалению, у меня нет доступа к этой книге! Википедия бесполезна, а Google еще ничего не нашел. : ^ (

Кто-нибудь знает алгоритм выполнения транзитивной редукции?


person i alarmed alien    schedule 06.11.2009    source источник


Ответы (7)


См. Гарри Сюй. «Алгоритм поиска минимального эквивалентного графа орграфа», Journal of the ACM, 22 (1): 11-16, январь 1975 г. Простой кубический алгоритм, приведенный ниже (с использованием матрицы путей N x N), достаточен для DAG, но Сюй обобщает его на циклические графы.

// 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;
person Alan Donovan    schedule 15.07.2011
comment
(для DAG) Другими словами: посмотрите на каждое ребро (i, j), удалите его, если есть причина не участвовать в транзитивной редукции. Не удаленные ребра должны находиться внутри переходной редукции. - person galath; 11.09.2011
comment
Согласно ссылке, которую вы цитируете, вы должны начинать с матрицы путей, а не с матрицы смежности. - person Michael Clerx; 03.05.2013
comment
Это работает не во всех случаях. В графе с ребрами (A, B), (B, C), (C, D) и (A, D) следует удалить последнее ребро (A, D). Это не так, потому что не существует комбинации двух ребер (m [i] [j] и m [j] [k]), которая ведет от A к D. - person Renze de Waal; 30.07.2013
comment
@MichaelClerx совершенно верно, я имел в виду матрицу путей. Спасибо, что указали на ошибку. Если у вас есть матрица смежности, сначала примените алгоритм Варшала, чтобы транзитивно закрыть ее. - person Alan Donovan; 25.11.2014

Основная суть используемого мной алгоритма транзитивной редукции заключается в следующем:


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
person i alarmed alien    schedule 03.03.2010
comment
Вам необходимо добавить if (x,z) != (x,y) && (x,z) != (y,z) перед delete edge..., чтобы избежать неправильного удаления в случае циклов. Помимо этого, и хотя было бы лучше иметь более быстрый алгоритм линейного времени, мне нравится этот ответ: красивый и простой. - person Joey Adams; 06.06.2010
comment
Кроме того, если в графе есть циклы, этот алгоритм не всегда будет производить минимальную транзитивную редукцию. Например, попробуйте это на [0,1,2,3,4,5], где A указывает на B для всех A и B (даже если они одинаковы). Должно получиться что-то вроде 0 - ›1 -› 2 - ›3 -› 4 - ›5 -› 0, но запуск этого алгоритма (с моей настройкой) приносит 5 - ›2 и 5 -› 4 в дополнение к 0 - ›... -› 5 - ›0. Запуск без моего твика вообще не дает ребер. - person Joey Adams; 07.06.2010
comment
Я должен был сказать, что мой код включал проверки идентичных ребер, о которых вы упомянули, а также что я работаю исключительно с DAG, поэтому циклы не являются проблемой. - person i alarmed alien; 10.06.2010
comment
Вы уверены в своем алгоритме транзитивного замыкания? Для этой задачи я бы использовал алгоритм Флойда-Уоршалла, который равен 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. Обратите внимание на различный порядок в x и y. Я думал, что порядок имеет значение. Это не так? - person galath; 11.09.2011
comment
Как отмечает cmn, этот алгоритм очищает ребра, которые соединяют узлы, которые также соединены путем, имеющим более двух ребер. Пример: A - ›B -› C - ›D; А - ›С; A- ›D. Алгоритм очистит A -› C, но не A - ›D. - person Penz; 28.06.2012
comment
Я реализовал здесь алгоритм транзитивной редукции, вкл. дополнительное условие if от @JoeyAdams, но иногда оно дает неверные (не только неоптимальные) результаты. У меня есть случай искажения DAG (без циклов!): V2 → V1, V3 → V1, V4 → V1, V5 → V1, V6 → V1, V2 → V3, V2 → V4, V2 → V5, V6 → V2 , V3 → V4, V3 → V5, V6 → V3, V4 → V5, V6 → V4, V6 → V5 сводится к V5 → V1, V6 → V1, V2 → V3, V6 → V2, V3 → V4, V4 → V5 , V6 → V4, а правильный результат должен быть V5 → V1, V2 → V3, V6 → V2, V3 → V4, V4 → V5. Так что же не так с этим алгоритмом и где найти правильный, работающий как с циклическими, так и с ациклическими ориентированными графами? - person kriegaex; 05.11.2020

статья в Википедии о транзитивном сокращении указывает на реализацию в GraphViz (с открытым исходным кодом) . Не совсем псевдокод, но, может быть, с чего начать?

LEDA включает алгоритм транзитивного сокращения. У меня нет копии книги LEDA больше, и эта функция могла быть добавлена ​​после публикации книги. Но если он там есть, то будет хорошее описание алгоритма.

Google указывает на алгоритм, который кто-то предложил для включения в Boost . Я не пробовал читать, так что может не правильно?

Также стоит взглянуть на это.

person Eric    schedule 07.11.2009
comment
Спасибо (с опозданием!) За ваш ответ. В конце концов, я написал автору книги по алгоритмам и попросил его проверить правильность написанного мной псевдокода, что он любезно и сделал. - person i alarmed alien; 03.03.2010
comment
исходный код tred практически не читается, спасибо к отсутствию каких-либо комментариев в коде. - person Bram Schoenmakers; 16.03.2012

Основываясь на ссылке, предоставленной Аланом Донованом, в котором говорится, что вы должны использовать матрицу путей (которая имеет 1, если есть путь от узла 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
person Michael Clerx    schedule 03.05.2013
comment
Я озадачен, потому что кажется, что, если вы удалите ребра в правильном порядке, вы можете сразу вернуться к исходной (избыточной) матрице смежности, применив алгоритм к матрице путей. Так что по сути вы ничего не добились. См. Этот пример: i.imgur.com/fbt6oK1.png Допустим, вы начинаете только с черного края, и, конечно же, вы хотите удалить точечный черный / зеленый край. Итак, вы добавляете красные края, чтобы получить матрицу путей. Затем вы удаляете красные края, потому что они оба могут быть удалены алгоритмом. А теперь ты застрял. - person Brian Gordon; 01.07.2013
comment
Использование m = [[0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1], [0, 0, 0, 0]] в качестве ввода работает нормально :) - person Michael Clerx; 02.07.2013
comment
Я думаю, что это может работать, если вам не повезет с тем, какие края удаляются в первую очередь. - person Brian Gordon; 02.07.2013
comment
Попробуйте, порядок не имеет значения. - person Michael Clerx; 02.07.2013
comment
Хорошо, извините, вы правы, я не могу найти ни одного случая, когда пунктирная черная / зеленая кромка удалялась раньше двух красных выступов. Когда я вернусь домой сегодня вечером, я попытаюсь понять, почему это происходит. - person Brian Gordon; 03.07.2013

Алгоритм «girlwithglasses» забывает, что избыточное ребро может охватывать цепочку из трех ребер. Чтобы исправить это, вычислите Q = R x R +, где R + - транзитивное замыкание, а затем удалите все ребра из R, которые появляются в Q. См. Также статью в Википедии.

person cmh    schedule 08.12.2010
comment
Вы можете предложить для этого какой-нибудь псевдокод? Алгоритм транзитивного сокращения, представленный ниже, будет работать на графе транзитивного замыкания, поэтому для ребра x-y, которое также может быть достигнуто с помощью x-A-B-y, у вас также будут x-A-y и x-B-y. - person i alarmed alien; 10.05.2011
comment
Что должен представлять Q? Что ты с этим делаешь? - person Brian Gordon; 01.07.2013

Алгоритм в глубину в псевдо-питоне:

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)

Алгоритм является неоптимальным, но решает проблему с несколькими ребрами, как в предыдущих решениях. Результаты очень похожи на то, что дает tred от graphviz.

person Penz    schedule 28.06.2012

портирован на 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
person cthiebaud    schedule 25.07.2015
comment
К вашему сведению, запрос на вытягивание с переработанной версией этого кода был отправлен, принят и объединен в jgrapht. github.com/jgrapht/jgrapht/commit/ - person cthiebaud; 24.08.2015
comment
К вашему сведению, алгоритм в JGraphT не работает, если график содержит циклы, см. issue # 667. Не могли бы вы проверить, что с ним не так? - person kriegaex; 05.11.2020