Нахождение всех путей между двумя вершинами с пределом веса в ориентированном графе

Я пытаюсь найти все пути между двумя вершинами с весом меньше N в ориентированном взвешенном графе, который может иметь петли, но не самопетли. Пока что я мог сделать это только с помощью _ 1_, а затем отфильтровать пути с весом больше N:

SimpleDirectedWeightedGraph<String, DefaultWeightedEdge> g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class);
AllDirectedPaths<String, DefaultWeightedEdge> allPaths = new AllDirectedPaths<>(g);
int maxWeight = 30;
List<GraphPath<String, DefaultWeightedEdge>> pathsLimitedByWeight = allPaths.getAllPaths(startVertex, endVertex, false, maxWeight / minGraphLatency)
    .filter(graphPath -> graphPath.getWeight() < maxWeight)
    .collect(Collectors.toList());

Этот подход явно неоптимален, потому что для больших графиков он медленный. Чтобы ограничить вывод и сделать его быстрее, я добавляю maxPathLength в AllDirectedPaths#getAllPaths, который я установил на максимальный вес, который путь может разделить на минимальный вес, который имеет край в графе, поскольку в моем случае вес края является целым числом и всегда больше 1.

Я подумывал об использовании KShortestSimplePaths с настраиваемым _ 6_, но поддерживает только простые пути, т. е. не допускает петель.

Какие еще варианты, если таковые имеются, у меня есть в jgrapht, которые можно использовать для решения поиска всех путей без необходимости самостоятельно перемещаться по графу.


person ouid    schedule 14.02.2021    source источник
comment
Этот вопрос обсуждается на мета.   -  person Sinatr    schedule 16.02.2021
comment
Я думаю, что одна вещь, которую следует учитывать, заключается в том, что независимо от того, как вы это делаете, если вы действительно хотите получить список всех путей, тогда это будет экспоненциальное время. Рассмотрим узлы s_i, u_i и d_i. Для каждого s_i у нас есть (s_i, u_i) и (s_i, d_i) в E, а также (u_ (i), s_ (i + 1)) и (d_ (i), s_ (i + 1)). Если вы нарисуете этот график, он должен быть достаточно ясным, чтобы увидеть, что существует экспоненциально много путей, поэтому эффективный алгоритм невозможен.   -  person Hive7    schedule 16.02.2021
comment
@ Hive7, я понимаю, что эффективный алгоритм невозможен, и что время получения всех путей будет экспоненциальным. В документации AllDirectedPaths, который реализует получение списка всех путей между двумя вершинами, говорится, что если все пути учтены (т.е. нет ограничения на максимальную длину пути), это может быть очень медленным из-за потенциально огромных вывод, и я понимаю, что, поскольку разрешены непростые пути, он будет еще медленнее с еще большим выводом. Я ищу API в jgrapht, похожий на AllDirectedPaths # getAllPaths (Graph, s, t, maxPathWeight)   -  person ouid    schedule 17.02.2021


Ответы (1)


Не существует алгоритма, позволяющего эффективно запрашивать все непростые пути между парой вершин. Путей может быть экспоненциально много. Представьте себе граф со следующими ребрами: (s, u), (u, v), (v, u), (u, t), где все ребра имеют длину 1. Теперь найдите все непростые пути из s в t. , с ограничением веса N. Вы получите следующие пути:

  • s,u,t
  • s,u,v,u,t
  • s,u,v,u,v,u,t
  • s,u,v,u,v,u,v,u,t
  • ....

Вы можете продолжать ездить на велосипеде [u, v, u], пока, наконец, не достигнете предела веса. Если это действительно то, что вы хотите, я бы порекомендовал реализовать простой алгоритм маркировки. Метка кодирует частичный путь. Метка хранит ссылку на свою предыдущую метку, ссылку на узел, с которым связана метка, а также стоимость, равную общей стоимости частичного пути, представленного меткой. Запустите алгоритм с создания метки для исходного узла s со стоимостью 0 и добавьте ее в очередь открытых меток. Во время каждой итерации алгоритма опрашивайте метку из открытой очереди, пока она не будет исчерпана. Для опрашиваемой метки L, связанной с узлом i и имеющей стоимость c, разверните метку: для каждого соседа j узла i создайте новую метку L ', которая указывает обратно на метку L, и установите ее стоимость равной c плюс вес края d_ij. Если стоимость новой этикетки L 'превышает доступный бюджет, выбросите этикетку. В противном случае, если j является целевым узлом, мы нашли новый путь, поэтому сохраните метку, чтобы мы могли восстановить путь позже. В противном случае добавьте L 'в очередь открытых этикеток. Ниже представлена ​​простая реализация этого алгоритма.

Примечания:

  1. Вышеупомянутый алгоритм разметки будет работать только тогда, когда граф относительно мал, N низкое или веса ребер высокие, поскольку количество возможных путей от s к t может расти очень быстро.
  2. Производительность вышеуказанного алгоритма можно немного улучшить, включив допустимую эвристику для вычисления наименьшего количества бюджета, необходимого для завершения пути от заданного узла к терминалу. Это позволит вам обрезать некоторые ярлыки.
  3. Вес всех кромок должен быть больше 0.
import org.jgrapht.*;
import org.jgrapht.graph.*;

import java.util.*;

public class NonSimplePaths<V,E> {

    public List<GraphPath<V, E>> computeNoneSimplePaths(Graph<V,E> graph, V source, V target, double budget){
        GraphTests.requireDirected(graph); //Require input graph to be directed
        if(source.equals(target))
            return Collections.emptyList();

        Label start = new Label(null, source, 0);
        Queue<Label> openQueue = new LinkedList<>(); //List of open labels
        List<Label> targetLabels = new LinkedList<>(); //Labels associated with target node
        openQueue.add(start);

        while(!openQueue.isEmpty()){
            Label openLabel = openQueue.poll();
            for(E e : graph.outgoingEdgesOf(openLabel.node)){
                double weight = graph.getEdgeWeight(e);
                V neighbor = Graphs.getOppositeVertex(graph, e, openLabel.node);

                //Check whether extension is possible
                if(openLabel.cost + weight <= budget){
                    Label label = new Label(openLabel, neighbor, openLabel.cost + weight); //Create new label
                    if(neighbor.equals(target)) //Found a new path from source to target
                        targetLabels.add(label);
                    else //Must continue extending the path until a complete path is found
                        openQueue.add(label);
                }
            }
        }

        //Every label in the targetLabels list corresponds to a unique path. Recreate paths by backtracking labels
        List<GraphPath<V,E>> paths = new ArrayList<>(targetLabels.size());
        for(Label label : targetLabels){ //Iterate over every path
            List<V> path = new LinkedList<>();
            double pathWeight = label.cost;
            do{
                path.add(label.node);
                label=label.pred;
            }while(label != null);
            Collections.reverse(path); //By backtracking the labels, we recoved the path in reverse order
            paths.add(new GraphWalk<>(graph, path, pathWeight));
        }

       return paths;
   }

    private final class Label{
        private final Label pred;
        private final V node;
        private final double cost;

        private Label(Label pred, V node, double cost) {
            this.pred = pred;
            this.node = node;
            this.cost = cost;
        }
    }

    public static void main(String[] args){
        Graph<String,DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class);
        Graphs.addAllVertices(graph, Arrays.asList("s","u","v","t"));
        graph.addEdge("s","u");
        graph.addEdge("u","t");
        graph.addEdge("u","v");
        graph.addEdge("v","u");
        graph.edgeSet().forEach(e -> graph.setEdgeWeight(e,1.0)); //Set weight of all edges to 1

        NonSimplePaths<String,DefaultWeightedEdge> nonSimplePaths = new NonSimplePaths<>();
        List<GraphPath<String,DefaultWeightedEdge>> paths = nonSimplePaths.computeNoneSimplePaths(graph, "s", "t", 10);
        for(GraphPath<String,DefaultWeightedEdge> path : paths)
            System.out.println(path+" cost: "+path.getWeight());
    }
}

Вывод приведенного выше примера кода:

[s, u, t] cost: 2.0
[s, u, v, u, t] cost: 4.0
[s, u, v, u, v, u, t] cost: 6.0
[s, u, v, u, v, u, v, u, t] cost: 8.0
[s, u, v, u, v, u, v, u, v, u, t] cost: 10.0

Улучшение производительности вышеуказанной реализации, например добавив допустимую эвристику, я оставляю в качестве упражнения ОП.

person Joris Kinable    schedule 16.02.2021
comment
Спасибо за Ваш ответ. Я понимаю, что не существует алгоритма для эффективного запроса всех непростых путей между парой вершин. Мой вопрос не в существовании такого алгоритма, а в реализации (неэффективного алгоритма) в конкретной библиотеке (jgrapht), которая уже предлагает нечто подобное AllDirectedPaths, который возвращает все пути между двумя вершинами, потенциально ограниченные максимальной длиной пути. Так что я имел в виду реализацию, которая позволяет ограничивать по весу, а не по длине. - person ouid; 17.02.2021
comment
@ouid То, что вы просили, нельзя сделать с существующими библиотеками в JGraphT. Я обновил свой ответ, добавив полный алгоритм, реализованный на Java с использованием JGraphT в качестве библиотеки резервного графа. - person Joris Kinable; 17.02.2021