алгоритмы графа: достижимость по карте смежности

У меня есть граф зависимостей, который я представил как Map<Node, Collection<Node>> (на языке Java или f(Node n) -> Collection[Node] как функцию; это сопоставление заданного узла n с набором узлов, зависящих от n). Граф потенциально цикличен*.

Учитывая список badlist узлов, я хотел бы решить проблему достижимости: т. е. сгенерировать Map<Node, Set<Node>> badmap, представляющий отображение каждого узла N в списке badlist на набор узлов, который включает N или другой узел, транзитивно зависящий от него.

Пример:

(x -> y means node y depends on node x)
n1 -> n2
n2 -> n3
n3 -> n1
n3 -> n5
n4 -> n2
n4 -> n5
n6 -> n1
n7 -> n1

Это можно представить как карту смежности {n1: [n2], n2: [n3], n3: [n1, n5], n4: [n2, n5], n6: [n1], n7: [n1]}.

Если badlist = [n4, n5, n1], то я ожидаю получить badmap = {n4: [n4, n2, n3, n1, n5], n5: [n5], n1: [n1, n2, n3, n5]}.

Я барахтаюсь в поиске ссылок на алгоритмы графов в Интернете, поэтому, если кто-нибудь может указать мне на эффективное описание алгоритма для достижимости, я был бы признателен. (Примером того, что мне не полезно, является http://www.cs.fit.edu/~wds/classes/cse5081/reach/reach.html, так как этот алгоритм должен определить, доступен ли конкретный узел A из определенного узла. Б.)

*циклический: если вам интересно, это потому, что он представляет типы C/C++, а структуры могут иметь члены, которые являются указателями на рассматриваемую структуру.


person Jason S    schedule 01.09.2011    source источник


Ответы (6)


В Питоне:

def reachable(graph, badlist):
    badmap = {}
    for root in badlist:
        stack = [root]
        visited = set()
        while stack:
            v = stack.pop()
            if v in visited: continue
            stack.extend(graph[v])
            visited.add(v)
        badmap[root] = visited
    return badmap
person quaint    schedule 01.09.2011
comment
хорошо, это довольно просто. Я почему-то зациклился на том факте, что приходится заново выполнять цикл for root in badlist без использования знаний, полученных при предыдущих выполнениях цикла. Так что, возможно, можно оптимизировать скорость... но то, что у вас есть, довольно просто. - person Jason S; 02.09.2011
comment
@Jason Это асимптотически оптимально для графов с ограниченной степенью входа (т. Е. Количеством различных типов структур, на которые ссылается структура). Я был бы удивлен, если бы узкое место не было в другом месте. - person quaint; 02.09.2011
comment
@Jason: Если вы хотите оптимизировать этот алгоритм для повышения скорости, поместите бит метки в саму вершину, а не ищите его во внешнем наборе visited. - person J D; 07.11.2011

вот что я в итоге использовал, основываясь на ответе @quaint:

(для удобства требуется несколько классов Guava)

static public <T> Set<T> findDependencies(
        T rootNode, 
        Multimap<T, T> dependencyGraph)
{
    Set<T> dependencies = Sets.newHashSet();
    LinkedList<T> todo = Lists.newLinkedList();
    for (T node = rootNode; node != null; node = todo.poll())
    {
        if (dependencies.contains(node))
            continue;
        dependencies.add(node);
        Collection<T> directDependencies = 
                dependencyGraph.get(node);
        if (directDependencies != null)
        todo.addAll(directDependencies);
    }
    return dependencies;
}
static public <T> Multimap<T,T> findDependencies(
        Iterable<T> rootNodes, 
        Multimap<T, T> dependencyGraph)
{
    Multimap<T, T> dependencies = HashMultimap.create();
    for (T rootNode : rootNodes)
        dependencies.putAll(rootNode, 
                findDependencies(rootNode, dependencyGraph));
    return dependencies;
}
static public void testDependencyFinder()
{
    Multimap<Integer, Integer> dependencyGraph = 
            HashMultimap.create();
    dependencyGraph.put(1, 2);
    dependencyGraph.put(2, 3);
    dependencyGraph.put(3, 1);
    dependencyGraph.put(3, 5);
    dependencyGraph.put(4, 2);
    dependencyGraph.put(4, 5);
    dependencyGraph.put(6, 1);
    dependencyGraph.put(7, 1);
    Multimap<Integer, Integer> dependencies = 
            findDependencies(ImmutableList.of(4, 5, 1), dependencyGraph);
    System.out.println(dependencies);
    // prints {1=[1, 2, 3, 5], 4=[1, 2, 3, 4, 5], 5=[5]}
}
person Jason S    schedule 01.09.2011

Возможно, вам следует построить матрицу достижимости из списка смежности для быстрого поиска. Я только что нашел документ Примечания к курсу для CS336: График Теория — Джаядев Мишра , описывающая, как построить матрицу достижимости из матрицы смежности.

Если A — ваша матрица смежности, матрица достижимости будет R = A + A² + ... + A^n, где n — количество узлов в графе. A², A³, ... можно рассчитать:

  • A² = A x A
  • A³ = A x A²
  • ...

Для матричного умножения используется логическое или вместо + и логическое и вместо x. Сложность O(n^4).

person Christian Ammer    schedule 01.09.2011
comment
+1 за любопытство, но мой badlist — это очень небольшое количество узлов по сравнению с общим количеством узлов в графе (обычно плохой список имеет размер 4–10, тогда как общее количество узлов исчисляется десятками тысяч), поэтому я Я не уверен, что это эффективно или легко реализовать для моих целей. - person Jason S; 02.09.2011

Обычный поиск в глубину или поиск в ширину добьются цели: выполните его один раз для каждого плохого узла.

person zvrba    schedule 01.09.2011

Вот рабочее решение Java:

// build the example graph
Map<Node, Collection<Node>> graph = new HashMap<Node, Collection<Node>>();
graph.put(n1, Arrays.asList(new Node[] {n2}));
graph.put(n2, Arrays.asList(new Node[] {n3}));
graph.put(n3, Arrays.asList(new Node[] {n1, n5}));
graph.put(n4, Arrays.asList(new Node[] {n2, n5}));
graph.put(n5, Arrays.asList(new Node[] {}));
graph.put(n6, Arrays.asList(new Node[] {n1}));
graph.put(n7, Arrays.asList(new Node[] {n1}));

// compute the badmap
Node[] badlist = {n4, n5, n1};
Map<Node, Collection<Node>> badmap = new HashMap<Node, Collection<Node>>();

for(Node bad : badlist) {
    Stack<Node> toExplore = new Stack<Node>();
    toExplore.push(bad);
    Collection<Node> reachable = new HashSet<Node>(toExplore);
    while(toExplore.size() > 0) {
        Node aNode = toExplore.pop();
        for(Node n : graph.get(aNode)) {
            if(! reachable.contains(n)) {
                reachable.add(n);
                toExplore.push(n);
            }
        }
    }

    badmap.put(bad, reachable);
}

System.out.println(badmap);
person ChrisJ    schedule 01.09.2011

Как и в случае с Кристианом Аммером, вы берете в качестве A матрицу смежности и используете логическую арифметику при выполнении следующих действий, где I — это единичная матрица.

    B = A + I;
    C = B * B;
    while (B != C) {
        B = C;
        C = B * B;
    }
    return B;

Кроме того, стандартное умножение матриц (как арифметическое, так и логическое) равно O(n^3), а не O(n^2). Но если n <= 64, вы можете как бы избавиться от одного фактора n, потому что вы можете выполнять 64-битные операции параллельно на современных 64-битных машинах. Для больших графов также полезен 64-битный параллелизм, но методы шейдеров могут быть даже лучше.

РЕДАКТИРОВАТЬ: можно выполнять 128 бит параллельно с инструкциями SSE, с AVX даже больше.

person Michiel    schedule 22.11.2016