Java: использование списка смежности для вычисления кратчайшего пути для всех пар?

Я пишу библиотеку Graph, которая имеет как список смежности, так и матричные реализации. Вот код, с которым я столкнулся в учебнике по структурам данных Java:

static void floyd(Graph<V,E> g) 
// post: g contains edge (a,b) if there is a path from a to b 
{
     Iterator<V> witer = g.iterator();   //vertex iterator
     while (witer.hasNext())
     {
        Iterator<V> uiter = g.iterator(); 
        V w = witer.next(); 
        while (uiter.hasNext())
        {
            Iterator<V> viter = g.iterator();
            V u = uiter.next(); 
            while (viter.hasNext())
            {
                V v = viter.next(); 
                if (g.containsEdge(u,w) && g.containsEdge(w,v)) 
                {
                     Edge<V,E> leg1 = g.getEdge(u,w);
                     Edge<V,E> leg2 = g.getEdge(w,v); 
                     int leg1Dist = leg1.label(); 
                     int leg2Dist = leg2.label(); 
                     int newDist = leg1Dist+leg2Dist;

                     if (g.containsEdge(u,v)) 
                     {
                         Edge<V,E> across = g.getEdge(u,v); 
                         int acrossDist = across.label(); 
                         if (newDist < acrossDist)
                           across.setLabel(newDist); 
                      } 
                      else 
                      {
                           g.addEdge(u,v,newDist);
                       }
                  }
             }
         }
     }

Но похоже, что он просто перезаписывает текущий край «самым коротким». Верна ли эта интерпретация? Я мог бы использовать некоторые разъяснения здесь.

Примечание. Вот некоторые из классов Edge:

public class Edge
{
/**
 * Two element array of vertex labels.
 * When necessary, first element is source.
 */
protected Object[] vLabel;  // labels of adjacent vertices
/**
 * Label associated with edge.  May be null.
 */
protected Object label;     // edge label
/**
 * Whether or not this edge has been visited.
 */
protected boolean visited;  // this edge visited
/**
 * Whether or not this edge is directed.
 */
protected boolean directed; // this edge directed

/**
 * Construct a (possibly directed) edge between two labeled
 * vertices.  When edge is directed, vtx1 specifies source.
 * When undirected, order of vertices is unimportant.  Label
 * on edge is any type, and may be null.
 * Edge is initially unvisited.
 *
 * @post edge associates vtx1 and vtx2; labeled with label
 *       directed if "directed" set true
 *
 * @param vtx1 The label of a vertex (source if directed).
 * @param vtx2 The label of another vertex (destination if directed).
 * @param label The label associated with the edge.
 * @param directed True iff this edge is directed.
 */
public Edge(Object vtx1, Object vtx2, Object label,
            boolean directed)
{
    vLabel = new Object[2];
    vLabel[0] = vtx1;
    vLabel[1] = vtx2;
    this.label = label;
    visited = false;
    this.directed = directed;
}

/**
 * Returns the first vertex (or source if directed).
 *
 * @post returns first node in edge
 * 
 * @return A vertex; if directed, the source.
 */
public Object here()
{
    return vLabel[0];
}

/**
 * Returns the second vertex (or source if undirected).
 *
 * @post returns second node in edge
 * 
 * @return A vertex; if directed, the destination.
 */
public Object there()
{
    return vLabel[1];
}

/**
 * Sets the label associated with the edge.  May be null.
 *
 * @post sets label of this edge to label 
 * 
 * @param label Any object to label edge, or null.
 */
public void setLabel(Object label)
{
    this.label = label;
}

/**
 * Get label associated with edge.
 *
 * @post returns label associated with this edge
 * 
 * @return The label found on the edge.
 */
public Object label()
{
    return label;
}

person Nathan    schedule 15.07.2010    source источник
comment
В качестве комментария к их примеру, viter, witer и uiter должны быть одними из самых простых имен переменных, которые можно спутать. Фу. i/j/k, по крайней мере, выглядят полностью отличными друг от друга, и в примере, похоже, даже u/v/w не используются в алфавитном порядке. ЧТО?   -  person Dean J    schedule 15.07.2010
comment
Не стреляй в посыльного, Дин. ;)   -  person Nathan    schedule 15.07.2010


Ответы (1)


Было бы легче понять, что вы делаете, если бы вы использовали матрицу для сохранения результата в матрице. Рассмотрим следующий простой взвешенный граф:

       2
1 +---------+ 2
  |\        |
  | -\      |
3 |   -\5   | 2
  |     -\  |
  |       -\|
3 +---------+ 4
        4

Теперь рассмотрим эту реализацию алгоритма Флойда-Уоршалла:

public Matrix floyd() {
  Matrix m = new Matrix(numVertices, numVertices, Integer.MAX_VALUE);
  for (int i = 1; i<=numVertices; i++) {
    EdgeNode edge = edges[i];
    while (edge != null) {
      m.setData(i, edge.getY(), edge.getWeight());
      edge = edge.getNext();
    }
    m.setData(i, i, 0);
  }
  for (int i = 1; i <= numVertices; i++) {
    for (int j = 1; j <= numVertices; j++) {
      for (int k = 1; k <= numVertices; k++) {
        if (m.getData(i, j) < Integer.MAX_VALUE && m.getData(i, k) < Integer.MAX_VALUE) {
          int through = m.getData(i, j) + m.getData(i, k);
          if (through < m.getData(j, k)) {
            m.setData(j, k, through);
          }
        }
      }
    }
  }
  return m;
}

Первая часть этого задает результат матрицы с Integer.MAX_VALUE. Установка здесь 0 приведет к неверному результату, но использование значений 1 и 0 (соответственно) подойдет для невзвешенного графика. Integer.MAX_VALUE существует просто для правильной проверки минимального значения.

Вторая часть является ключевой частью алгоритма. Он смотрит на расстояние между двумя точками (i,k), сравнивая его с расстоянием (i,j) + (j,K) для всех вершин j. Если непрямой путь меньше, он подставляется в матрицу как кратчайший путь и так далее.

Результат этого алгоритма на приведенном выше (очень простом) графике:

[ 0 2 3 5 ]
[ 2 0 5 3 ]
[ 3 5 0 4 ]
[ 5 3 4 0 ]

Это говорит вам о кратчайшем расстоянии между любой парой вершин. Примечание. Я присвоил результату (i,i) значение 0, так как вы можете утверждать, что расстояние от любого узла до самого себя равно 0. Вы можете достаточно легко исключить это предположение и получить следующий результат:

[ 4 2 3 5 ]
[ 2 4 5 3 ]
[ 3 5 6 4 ]
[ 5 3 4 6 ]

Таким образом, узел 3 сам по себе находится на расстоянии 6, поскольку он проходит 3-> 1-> 3 как кратчайший путь. Это было бы намного интереснее в ориентированном графе, с которым Floyd's может справиться.

Алгоритм Флойда работает за O(n3). Он не будет восстанавливать фактический путь между каждой парой точек, а только общее расстояние (вес). Вы можете использовать алгоритм Дейкстры между каждой парой вершин для построения фактических путей, что довольно интересно. также O(n3), но имеет тенденцию быть медленнее в реальном мире, поскольку вычисления Флойда довольно просты.

Ваш алгоритм использует список смежности вместо матрицы для реализации этого алгоритма, что немного запутывает проблему. Моя версия использует Integer.MAX_VALUE в качестве контрольного значения для обозначения отсутствия пути (еще не рассчитанного), тогда как ваша использует отсутствие края для того же самого. Кроме этого, это точно так же.

person cletus    schedule 15.07.2010
comment
Я ценю вашу тщательность здесь, однако мое замешательство в утверждении if(g.containsEdge(u,v)) внизу. В частности, похоже, что он выполняет обратный алгоритм Флойда, ища ребра (u, w) и (w, v) и сравнивая их с неучтенным ребром (u, v). Вместо того, чтобы смотреть на края (u, w) и (w, v) и сравнивать сумму с краем (u, v), я просто не уверен, является ли этот метод действительным или нет. Верна ли эта оценка? - person Nathan; 15.07.2010
comment
#Натан в моих версиях использует Integer.MAX_VALUE для обозначения отсутствия пути. В ваших версиях отсутствие края используется для обозначения отсутствия пути, поэтому g.containsEdge() спрашивает, есть ли путь между этими узлами? Не обязательно прямой путь. Просто любой путь. - person cletus; 16.07.2010