【问题标题】:How is a section of code affecting the algorithm?一段代码如何影响算法?
【发布时间】:2016-08-08 08:25:32
【问题描述】:

如果这是一个愚蠢或错误的问题,我很抱歉,我在 Java 中找到了用于短路径算法的解决方案。代码如下:

import java.io.*;
import java.util.*;

public class Dijkstra {
   private static final Graph.Edge[] GRAPH = {
      new Graph.Edge("a", "b", 7),
      new Graph.Edge("a", "c", 9),
      new Graph.Edge("a", "f", 14),
      new Graph.Edge("b", "c", 10),
      new Graph.Edge("b", "d", 15),
      new Graph.Edge("c", "d", 11),
      new Graph.Edge("c", "f", 2),
      new Graph.Edge("d", "e", 6),
      new Graph.Edge("e", "f", 9),
   };
   private static final String START = "a";
   private static final String END = "e";

   public static void main(String[] args) {
      Graph g = new Graph(GRAPH);
      g.dijkstra(START);
      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<>();

      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) {
         return Integer.compare(dist, other.dist);
      }
   }

   /** Builds a graph from a set of edges */
   public Graph(Edge[] edges) {
      graph = new HashMap<>(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 do this 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<>();

      // 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();
      }
   }
}

我理解了这个算法的大部分内容,但是private void dijkstra(final NavigableSet&lt;Vertex&gt; q)这个方法让我对下面的问题感到困惑:

  1. 其余代码如何评估它,因为它没有任何返回方法?
  2. 使用 NavigableSet / TreeSet 是否比使用 PriorityQueue 更好?

另外,我还有一个关于在 Vertex 类中被覆盖的 compareTo 方法的问题,它是如何被调用的?

谢谢

【问题讨论】:

  • 这是两个问题,不是一个。 “更好”在哪方面?
  • @Raedwald 算法效率更高。

标签: java algorithm priority-queue treeset


【解决方案1】:

其余代码如何评估它,因为它没有任何返回方法?

具体做评估的部分是:

u = q.pollFirst();

影响q.pollFirst() 返回的部分是:

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

v 节点从集合中移除,它的距离正在更新,然后重新添加到集合中。

更新的距离是最重要的部分。

可能需要删除然后重新添加的节点,以便节点按新距离值而不是旧距离值排序。

所有的点都是如此q.pollFirst()返回距离最短的节点。

使用 NavigableSet / TreeSet 是否比使用 PriorityQueue 更好?

“更好”是主观的。您是在寻找速度、更好的界面还是特定的数据结构?

据我了解,TreeSetPriorityQueue 都使用Comparable 对节点进行排序,因此从这个意义上说它们的工作方式相似。

此外,TreeSet 是一个集合,因此节点在集合中只能存在一次,而PriorityQueue 是一个队列,可以多次插入同一个节点。

在这种情况下,一组似乎适用于 dijkstra 算法。

Vertex 类中重写的 compareTo 方法是如何被调用的?

TreeSet 内部使用compareTo 函数在添加节点时对其进行排序。

将新节点与集合中已有的节点进行比较。 Vertex#compareTo 提供了一种算法来确定两个Vertex 如何相互比较。在这种情况下,比较Vertex的距离值。

这也暗示了为什么节点被删除并重新添加到dijkstra(final NavigableSet&lt;Vertex&gt; q) 函数中的集合中。

【讨论】:

    【解决方案2】:
    1. 其余代码如何评估它,因为它没有任何返回方法?

    方法的参数是一个可变容器,被算法的各个部分引用。

     final Vertex source = graph.get(startName);
      NavigableSet<Vertex> q = new TreeSet<>();
    
      // 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);
    
    1. 使用 NavigableSet / TreeSet 是否比使用 PriorityQueue 更好? 你可以在这个问题中找到答案Difference between PriorityQueue and TreeSet in Java?

    【讨论】:

    • 该方法的参数是一个NavigableSet,但在算法的其他部分没有引用它,这让我感到困惑。我想让该方法返回 NavigableSet 以便使用它。请您再解释一下好吗?
    • 感谢编辑,我是Java新手,只想提高算法知识。所以,作为参数传递给 dijkstra 的 'q' 没有被返回,但它仍然用于确定路径,这让我感到困惑,希望我能说清楚。
    • v.dist = alternateDist;此行更改最初为 0 或 Integer.MAV_VALUE 的顶点的 dist 值。
    • 我理解,甚至是 v.previous 部分,如何在 dinkstra 方法中计算最短距离对我来说也很清楚,但不是其他地方如何打印路径, printPath() 方法如何使用这个 q NavigableSet.
    【解决方案3】:

    1) 有两个同名方法:

    public void dijkstra(String startName)
    private void dijkstra(final NavigableSet<Vertex> q)
    

    如果您调用dijkstra("a"),将调用第一个方法,如果您调用diijkstra(q),其中qNavigableSet&lt;Vertex&gt;,则将调用第二个方法。


    2) 这取决于用于实现TreeSetPriorityQueue 的底层数据结构,以及输入Graph。对于某些输入图,未排序数组的执行速度可能比最小堆快。


    3) 我怀疑添加到NavigableSet 的对象需要有一个有效的compareTo() 方法,这样NavigableSet 才能发挥它的魔力。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-09
      • 2013-12-05
      • 1970-01-01
      • 2017-04-22
      • 2020-11-29
      • 1970-01-01
      • 2023-03-26
      • 2011-11-20
      相关资源
      最近更新 更多