【问题标题】:Edge disjoint shortest pair algorithm in Java?Java中的边缘不相交最短对算法?
【发布时间】:2015-02-15 18:04:33
【问题描述】:

我试图找到给定顶点对之间的最短边不相交路径对,我正在关注algorithm,我猜它通常是Suurballe's algorithm

算法如下:

  • 对给定的顶点对运行最短路径算法(我使用的是 Dijkstra 算法)
  • 将最短路径的每条边(相当于两个相反方向的弧)替换为指向源顶点的单个弧
  • 将上述每条弧的长度设为负数
  • 运行最短路径算法(注意:该算法应接受负成本)
  • 擦除找到的两条路径的重叠边缘,并反转第一条最短路径上剩余弧的方向,使其上的每条弧现在都指向汇顶点。所需的路径对结果。

在该维基百科中,第一步是找到源节点和目标节点之间的最短路径,我可以使用Dijkstra Algorithm 正确地做到这一点,如下面的代码所示 -

public class DijkstraAlgorithm {

    private static final Graph.Edge[] GRAPH = { 
        new Graph.Edge("A", "G", 8), 
        new Graph.Edge("A", "B", 1), 
        new Graph.Edge("A", "E", 1), 
        new Graph.Edge("B", "C", 1), 
        new Graph.Edge("B", "E", 1),
        new Graph.Edge("B", "F", 2),
        new Graph.Edge("C", "G", 1),
        new Graph.Edge("C", "D", 1),
        new Graph.Edge("D", "F", 1),
        new Graph.Edge("D", "Z", 1),
        new Graph.Edge("E", "F", 4),
        new Graph.Edge("F", "Z", 4),
        new Graph.Edge("G", "Z", 2),
    };

    private static final String START = "A";
    private static final String END = "Z";

    public static void main(String[] args) {
        Graph g = new Graph(GRAPH);
        g.dijkstra(START);
        //  print the shortest path using Dijkstra algorithm
        g.printPath(END);
        //        g.printAllPaths();
    }
}


class Graph {
    private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges

    /** One edge of the graph (only used by Graph constructor) */
    public static class Edge {
        public final String v1, v2;
        public final int dist;

        public Edge(String v1, String v2, int dist) {
            this.v1 = v1;
            this.v2 = v2;
            this.dist = dist;
        }
    }

    /** One vertex of the graph, complete with mappings to neighbouring vertices */
    public static class Vertex implements Comparable<Vertex> {
        public final String name;
        public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
        public Vertex previous = null;
        public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();

        public Vertex(String name) {
            this.name = name;
        }

        private void printPath() {
            if (this == this.previous) {
                System.out.printf("%s", this.name);
            } else if (this.previous == null) {
                System.out.printf("%s(unreached)", this.name);
            } else {
                this.previous.printPath();
                System.out.printf(" -> %s(%d)", this.name, this.dist);
            }
        }

        public int compareTo(Vertex other) {
            if (dist==other.dist)
                return name.compareTo(other.name);
            return Integer.compare(dist, other.dist);
        }
    }

    /** Builds a graph from a set of edges */
    public Graph(Edge[] edges) {
        graph = new HashMap<String, Vertex>(edges.length);

        //one pass to find all vertices
        for (Edge e : edges) {
            if (!graph.containsKey(e.v1))
                graph.put(e.v1, new Vertex(e.v1));
            if (!graph.containsKey(e.v2))
                graph.put(e.v2, new Vertex(e.v2));
        }

        //another pass to set neighbouring vertices
        for (Edge e : edges) {
            graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
            graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
        }
    }

    /** Runs dijkstra using a specified source vertex */
    public void dijkstra(String startName) {
        if (!graph.containsKey(startName)) {
            System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
            return;
        }
        final Vertex source = graph.get(startName);
        NavigableSet<Vertex> q = new TreeSet<Vertex>();

        // set-up vertices
        for (Vertex v : graph.values()) {
            v.previous = v == source ? source : null;
            v.dist = v == source ? 0 : Integer.MAX_VALUE;
            q.add(v);
        }

        dijkstra(q);
    }

    /** Implementation of dijkstra's algorithm using a binary heap. */
    private void dijkstra(final NavigableSet<Vertex> q) {
        Vertex u, v;
        while (!q.isEmpty()) {

            u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
            if (u.dist == Integer.MAX_VALUE)
                break; // we can ignore u (and any other remaining vertices) since they are unreachable

            //look at distances to each neighbour
            for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
                v = a.getKey(); //the neighbour in this iteration

                final int alternateDist = u.dist + a.getValue();
                if (alternateDist < v.dist) { // shorter path to neighbour found
                    q.remove(v);
                    v.dist = alternateDist;
                    v.previous = u;
                    q.add(v);
                }
            }
        }
    }

    /** Prints a path from the source to the specified vertex */
    public void printPath(String endName) {
        if (!graph.containsKey(endName)) {
            System.err.printf("Graph doesn't contain end vertex \"%s\"\n", endName);
            return;
        }

        graph.get(endName).printPath();
        System.out.println();
    }

    /** Prints the path from the source to every vertex (output order is not guaranteed) */
    public void printAllPaths() {
        for (Vertex v : graph.values()) {
            v.printPath();
            System.out.println();
        }
    }
}

现在我被困在执行该算法中的剩余步骤,以便我可以获得给定顶点对之间的最短边不相交路径对

从节点 A 到节点 Z 的最短路径是 ABCDZ,而最短的对是 ABCGZAEBFDZ

【问题讨论】:

    标签: java algorithm graph dijkstra


    【解决方案1】:

    我不会写代码,但我可以向你解释如何解决这个问题,并给出一个基本的想法,为什么它会起作用。我将使用术语 sourcesink 来表示您正在搜索路径之间的两个节点。

    首先,它为什么有效。正如您在示例中注意到的那样,最短路径对不一定包含其中的单个最短路径。此外,如果您找到最短路径并将其删除,您会发现自己处于一种情况,即 no 从源到接收器的路径与您刚刚找到的最短路径边缘不相交。所以我们需要找到一种方法来改变我们在构建第二条路径时找到的第一条路径。事实上,这正是我们通过添加负权重实现的。

    让我们用你的例子来考虑一下。您运行了第一个 dijkstra,它为您找到了路径 ABCDZ。现在,当我们运行第二个 dijkstra 时,我们想要找到另一个路径 AEBFDZ,同时将 ABCDZ 修改为 ABCGZ。它是这样发生的:在第一个 dijstra 之后,您反转该路径中的所有边缘并否定它们的权重。例如,权重为 1 的边 A-&gt;B 变为权重 -1 的边 B-&gt;A。这应该很容易做到——你已经有了恢复路径的逻辑。当你恢复路径时,移除它所包含的边,然后用负重反向添加它们。

    对于您的特定示例,我们只关心权重为 1 的边 C-&gt;D。在我们运行第一个 dijkstra 后,它被反转,变成了权重为 -1 的边 D-&gt;C。现在,当我们试图找到第二条最短路径时,它会找到一条路径AEFDCGZ。请注意,它包含边 D-&gt;C,即我们刚刚添加的边。在第二条路径中使用这种具有负权重的边意味着什么?好吧,试着把它画在纸上,你会看到第一个路径像A-B-C ----- D-Z,第二个像A-E-F-D ---- C-G-Z。如果您绘制它,您会注意到您可以从两条路径中删除该边(C-D),并交换它们的尾巴。当你这样做时,路径权重的总和不会改变(因为第一条路径的边权重为正,第二条路径的权重为负),导致两条路径 A-B-C-G-ZA-E-F-D-Z,准确地说您正在寻找的两条路径。

    现在让我们看看如何明智地解决这个问题。您将需要三个独立的阶段。我本可以为你编写代码,但我相信你会通过自己的攻击学到更多。

    第 1 阶段。您需要实现反转边缘的逻辑。正如我所说,这非常简单。您只需在第一个 dijkstra 之后恢复路径(您已经有这样做的逻辑),并且对于该路径中的每条边,您将其从图中删除,然后将其反向添加并取反。

    第 2 阶段。您需要一个可以在具有负权重的图中找到最短路径的函数。请务必注意,dijkstra 适用于具有负权重的图。所以这里我们有两种方法:方法(a)是使用bellman-ford算法。它比 dijkstra 慢,但确实如此:在具有负权重的图中找到最短路径。方法 (b) 鲜为人知,但速度更快,并且利用了我们引入这些负权重的方式。其工作方式如下:

    1. 首先,当您运行第一个 dijkstra 时,当您到达 sink 时不要停止,继续遍历图形并将距离分配给节点。在第一个 dijskstra 结束时,每个节点将与分配给它的源有一段距离。将这些值复制到一个名为pt 的新数组中。我们将这些值称为“潜力”。

    2. 删除属于第一条路径的边,并添加它们的反向副本和否定副本(执行阶段 1)

    3. 之后,将图中每条边的权重w[i, j]改为w'[i, j] = w[i, j] + pt[i] - pt[j],其中pt[i]是顶点i的势。有趣的是,权重为w' 的新图将有两个属性:它不会有任何负权重,并且原始图中源和汇之间的最短路径将是新图中源和汇之间的最短路径(以及反之亦然)。现在您可以在这个新图中运行 dijkstra(因为它没有负权重),并确保您在其中找到的最短路径与原始图中的最短路径相对应。

    此时,您应该能够为示例图获取路径 A-B-C-D-ZA-E-F-D-C-G-Z。请务必在进入第 3 阶段之前到达此阶段。

    第 3 阶段:当您完成第 2 阶段时,最后一个阶段将是实施正确的路径恢复。给定来自第 2 阶段的两条路径,您将需要找到第二条路径中使用的所有负权重,并重新连接路径之间的边。还有一个更简单的替代方案,即每条边都跟踪它是否属于两条路径之一。如果将某个边缘添加到具有正边缘的两条路径之一,则将其标记为属于其中一条路径,并且当您添加负权重时,将其标记为不属于。在您的示例中,当您找到第一条路径时,边缘 C-D 将首先被标记为属于其中一条路径,然后在您找到第二条路径并将 D-C 添加到它时标记为不属于。当你有这样的标记时,你可以通过以任何顺序从源到接收器遍历标记的边缘来恢复两条路径。

    编辑:这是 java 代码。请注意,除了我介绍的新方法之外,实际的 dijkstra 方法也发生了变化。也就是说,它现在在计算alternativeDist 时使用势能。我恢复路径的方式似乎有点过于复杂,可能有更简单的方法。我目前存储属于答案的所有边的树集。如果我试图添加一条边,它的反向已经在答案中,我改为将其从答案中删除(它是对否定边的遍历)。然后我只是根据该树集恢复答案。

    import java.util.*;
    
    public class DijkstraAlgorithm {
    
        private static final Graph.Edge[] GRAPH = { 
            new Graph.Edge("A", "G", 8), 
            new Graph.Edge("A", "B", 1), 
            new Graph.Edge("A", "E", 1), 
            new Graph.Edge("B", "C", 1), 
            new Graph.Edge("B", "E", 1),
            new Graph.Edge("B", "F", 2),
            new Graph.Edge("C", "G", 1),
            new Graph.Edge("C", "D", 1),
            new Graph.Edge("D", "F", 1),
            new Graph.Edge("D", "Z", 1),
            new Graph.Edge("E", "F", 4),
            new Graph.Edge("F", "Z", 4),
            new Graph.Edge("G", "Z", 2),
        };
    
        private static final String START = "A";
        private static final String END = "Z";
    
        public static void main(String[] args) {
            Graph g = new Graph(GRAPH);
            g.dijkstra(START);
            g.restorePath(END);
            g.revertEdges(END);
            g.assignPotentials();
            g.dijkstra(START);
            g.restorePath(END);
    
            g.printPaths(START, END);
        }
    }
    
    
    class Graph {
        private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges
    
        /** One edge of the graph (only used by Graph constructor) */
        public static class Edge implements Comparable<Edge> {
            public final String v1, v2;
            public final int dist;
    
            public Edge(String v1, String v2, int dist) {
                this.v1 = v1;
                this.v2 = v2;
                this.dist = dist;
            }
    
            public int compareTo(Edge other) {
                if (v1.equals(other.v1))
                    return v2.compareTo(other.v2);
                return v1.compareTo(other.v1);
            }
        }
    
        private TreeSet<Edge> answer = new TreeSet<Edge>(); // stores all the edges in the answer
    
        /** One vertex of the graph, complete with mappings to neighbouring vertices */
        public static class Vertex implements Comparable<Vertex> {
            public final String name;
            public int potential = 0; // is assigned to dist before the second dijkstra
            public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
            public Vertex previous = null;
            public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();
    
            public Vertex(String name) {
                this.name = name;
            }
    
            public int compareTo(Vertex other) {
                if (dist==other.dist)
                    return name.compareTo(other.name);
                return Integer.compare(dist, other.dist);
            }
        }
    
        /** Builds a graph from a set of edges */
        public Graph(Edge[] edges) {
            graph = new HashMap<String, Vertex>(edges.length);
    
            //one pass to find all vertices
            for (Edge e : edges) {
                if (!graph.containsKey(e.v1))
                    graph.put(e.v1, new Vertex(e.v1));
                if (!graph.containsKey(e.v2))
                    graph.put(e.v2, new Vertex(e.v2));
            }
    
            //another pass to set neighbouring vertices
            for (Edge e : edges) {
                graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
                graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
            }
        }
    
        /** Runs dijkstra using a specified source vertex */
        public void dijkstra(String startName) {
            if (!graph.containsKey(startName)) {
                System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
                return;
            }
            final Vertex source = graph.get(startName);
            NavigableSet<Vertex> q = new TreeSet<Vertex>();
    
            // set-up vertices
            for (Vertex v : graph.values()) {
                v.previous = v == source ? source : null;
                v.dist = v == source ? 0 : Integer.MAX_VALUE;
                q.add(v);
            }
    
            dijkstra(q);
        }
    
        /** Implementation of dijkstra's algorithm using a binary heap. */
        private void dijkstra(final NavigableSet<Vertex> q) {
            Vertex u, v;
            while (!q.isEmpty()) {
    
                u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
                if (u.dist == Integer.MAX_VALUE)
                    break; // we can ignore u (and any other remaining vertices) since they are unreachable
    
                //look at distances to each neighbour
                for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
                    v = a.getKey(); //the neighbour in this iteration
    
                    final int alternateDist = u.dist + a.getValue() + u.potential - v.potential;
                    if (alternateDist < v.dist) { // shorter path to neighbour found
                        q.remove(v);
                        v.dist = alternateDist;
                        v.previous = u;
                        q.add(v);
                    }
                }
            }
        }
    
        /** Prints a path from the source to the specified vertex */
        public void revertEdges(String endName) {
            Vertex v = graph.get(endName);
            while (v.previous != null && v.previous != v) {
                Vertex w = v.previous;
                int weight = v.neighbours.get(w);
                v.neighbours.remove(w);
                w.neighbours.remove(v);
    
                v.neighbours.put(w, - weight);
    
                v = w;
            }
        }
    
        public void assignPotentials() {
            for (Vertex v : graph.values()) {
                v.potential = v.dist;
            }
        }
    
        /** Stores the path found by dijkstra into the answer */
        public void restorePath(String endName) {
            Vertex v = graph.get(endName);
            while (v.previous != null && v.previous != v) {
                String from = v.previous.name;
                String to = v.name;
                if (answer.contains(new Edge(to, from, 0))) {
                    answer.remove(new Edge(to, from, 0));
                }
                else {
                    answer.add(new Edge(from, to, 0));
                }
                v = v.previous;
            }
        }
    
        /** Restores and prints one path based on `answer` dictionary, and removes the edges restored from the answer */
        public void printOnePath(String startName, String endName) {
            Vertex from = graph.get(startName);
            Vertex to = graph.get(endName);
            Vertex cur = from;
            do {
                System.out.printf("%s -> ", cur.name);
    
                Edge e = answer.ceiling(new Edge(cur.name, "", 0));
                answer.remove(e);
    
                cur = graph.get(e.v2);
            } while (cur != to);
            System.out.println(to.name);
        }
    
        /** Restores and prints paths based on `answer` dicrionary */
        public void printPaths(String startName, String endName) {
            printOnePath(startName, endName);
            printOnePath(startName, endName);
        }
    }
    

    【讨论】:

    • 非常感谢 Ishamael 的详细建议。它确实有很大帮助。发布问题后,我开始研究它,看看我是否可以自己做。但看起来我无法进一步进行。我正试图做你提到的第一步,但无法让它继续工作。到目前为止,我正在尽我所能。
    • 对于第一阶段,以下几点可能会有所帮助:a) 您的图形不是定向的,但是当您添加否定边时,它们需要定向(与您的任何方向相反的方向遍历实际边缘)。 b) 您的 printPath 函数已经具有遍历路径边缘的逻辑。
    • 所以你要做的是遍历路径的所有边(类似于 printPath),然后为每个边删除它(如果边是 A->B,则从邻域中删除“B”[ 'A'] 并从邻域 ['B'] 中删除 'A')。最后,向后添加一个边,在这种情况下,将'A'添加回具有负权重的邻域['B']。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-23
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-30
    • 1970-01-01
    相关资源
    最近更新 更多