Для первой части вопроса я придумал свой собственный ответ здесь с помощью ответа на математическом сайте.
Для второй части вопроса:Следуя предложениям, данным в других ответах, я реализовал в Ruby (i) алгоритм Флойда-Варшалла для расчета транзитивного замыкания, (ii) композиции и (iii) транзитивного сокращения с использованием формулы R ^ -= R - R \ cdot R ^ +.
module Digraph; module_function
def vertices graph; graph.flatten(1).uniq end
## Floyd-Warshall algorithm
def transitive_closure graph
vs = vertices(graph)
path = graph.inject({}){|path, e| path[e] = true; path}
vs.each{|k| vs.each{|i| vs.each{|j| path[[i, j]] ||= true if path[[i, k]] && path[[k, j]]}}}
path.keys
end
def compose graph1, graph2
vs = (vertices(graph1) + vertices(graph2)).uniq
path1 = graph1.inject({}){|path, e| path[e] = true; path}
path2 = graph2.inject({}){|path, e| path[e] = true; path}
path = {}
vs.each{|k| vs.each{|i| vs.each{|j| path[[i, j]] ||= true if path1[[i, k]] && path2[[k, j]]}}}
path.keys
end
def transitive_reduction graph
graph - compose(graph, transitive_closure(graph))
end
end
Примеры использования:
Digraph.transitive_closure([[1, 2], [2, 3], [3, 4]])
#=> [[1, 2], [2, 3], [3, 4], [1, 3], [1, 4], [2, 4]]
Digraph.compose([[1, 2], [2, 3]], [[2, 4], [3, 5]])
#=> [[1, 4], [2, 5]]
Digraph.transitive_reduction([[1, 2], [2, 3], [3, 4], [1, 3], [1, 4], [2, 4]])
#=> [[1, 2], [2, 3], [3, 4]]