2014-11-30 6 views
0

Я реализую алгоритм Ford-Fulkerson, но у меня есть некоторые проблемы при обновлении графика после фазы увеличения. Моя структура данных не делает это легким, я думаю.Обновить график в Ford-Fulkerson

Для представления графа я использую это:

private Map<Vertex, ArrayList<Edge>> outgoingEdges; 

То есть, я связываю в каждой вершине свой список исходящих ребер.

Чтобы управлять обратными краями, я связал «противоположный» край для каждого ребра на графике.

Любое предложение оценивается.

public class FF { 

    /** 
    * Associates each Vertex with his list of outgoing edges 
    */ 
    private Map<Vertex, ArrayList<Edge>> outgoingEdges; 

    public FF() { 
     outgoingEdges = new HashMap<Vertex, ArrayList<Edge>>(); 
    } 

    /** 
    * Returns the nodes of the graph 
    */ 
    public Collection<Vertex> getNodes() { 
     return outgoingEdges.keySet(); 
    } 

    /** 
    * Returns the outgoing edges of a node 
    */ 
    public Collection<Edge> getIncidentEdges(Vertex v) { 
     return outgoingEdges.get(v); 
    } 

    /** 
    * Adds a new edge to the graph 
    */ 
    public void insertEdge(Vertex source, Vertex destination, float capacity) throws Exception { 
     if (!(outgoingEdges.containsKey(source) && outgoingEdges.containsKey(destination))) 
      throw new Exception("Unable to add the edge"); 

     Edge e = new Edge(source, destination, capacity); 
     Edge opposite = new Edge(destination, source, capacity); 
     outgoingEdges.get(source).add(e); 
     outgoingEdges.get(destination).add(opposite); 
     e.setOpposite(opposite); 
     opposite.setOpposite(e); 
    } 

    /** 
    * Adds a new node to the graph 
    */ 
    public void insertNode(Vertex v) { 
     if (!outgoingEdges.containsKey(v)) 
      outgoingEdges.put(v, new ArrayList<Edge>()); 
    } 

    /** 
    * Ford-Fulkerson algorithm 
    * 
    * @return max flow 
    */ 
    public int fordFulkerson(Vertex source, Vertex destination) { 
     List<Edge> path = new ArrayList<Edge>(); 
     int maxFlow = 0; 
     while(bfs(source, destination, path)) { 
      // finds the bottleneck 
      float minCap = bottleneck(path); 
      // updates the maxFlow 
      maxFlow += minCap;    
      // updates the graph <-- this updates only the local list path, not the graph! 
      for(Edge e : path) { 
       try { 
        e.addFlow(minCap); 
        e.getOpposite().addFlow(-minCap); 
       } catch (Exception e1) { 
        e1.printStackTrace(); 
       } 
      } 
      path.clear(); 
     } 
     return maxFlow; 
    } 

    /** 
    * @param Path of which we have to find the bottleneck 
    * @return bottleneck of the path 
    */ 
    private float bottleneck(List<Edge> path) { 
     float min = Integer.MAX_VALUE; 
     for(Edge e : path) { 
      float capacity = e.getCapacity(); 
      if(capacity <= min) { 
       min = capacity; 
      } 
     } 
     return min; 
    } 

    /** 
    * BFS to obtain a path from the source to the destination 
    * 
    * @param source 
    * @param destination 
    * @param path 
    * @return 
    */ 
    private boolean bfs(Vertex source, Vertex destination, List<Edge> path) { 
     Queue<Vertex> queue = new LinkedList<Vertex>(); 
     List<Vertex> visited = new ArrayList<Vertex>(); // list of visited vertexes 
     queue.add(source); 
     //source.setVisited(true); 
     visited.add(source); 
     while(!queue.isEmpty()) { 
      Vertex d = queue.remove(); 
      if(!d.equals(destination)) { 
       ArrayList<Edge> d_outgoingEdges = outgoingEdges.get(d); 
       for(Edge e : d_outgoingEdges) { 
        if(e.getCapacity() - e.getFlow() > 0) { // there is still available flow 
         Vertex u = e.getDestination(); 
         if(!visited.contains(u)) { 
          //u.setVisited(true); 
          visited.add(u); 
          queue.add(u); 
          path.add(e); 
         } 
        } 
       } 
      } 
     } 
     if(visited.contains(destination)) { 
      return true; 
     } 
     return false; 
    } 
} 

Край

public class Edge { 

    private Vertex source; 
    private Vertex destination; 
    private float flow; 
    private final float capacity; 
    private Edge opposite; 

    public Edge(Vertex source, Vertex destination, float capacity) { 
     this.source = source; 
     this.destination = destination; 
     this.capacity = capacity; 
    } 

    public Edge getOpposite() { 
     return opposite; 
    } 

    public void setOpposite(Edge e) { 
     opposite = e; 
    } 

    public void setSource(Vertex v) { 
     source = v; 
    } 

    public void setDestination(Vertex v) { 
     destination = v; 
    } 

    public void addFlow(float f) throws Exception { 
     if(flow == capacity) { 
      throw new Exception("Unable to add flow"); 
     } 
     flow += f; 
    } 

    public Vertex getSource() { 
     return source; 
    } 

    public Vertex getDestination() { 
     return destination; 
    } 

    public float getFlow() { 
     return flow; 
    } 

    public float getCapacity() { 
     return capacity; 
    } 

    public boolean equals(Object o) { 
     Edge e = (Edge)o; 
     return e.getSource().equals(this.getSource()) &&  e.getDestination().equals(this.getDestination()); 
    } 
} 

Vertex

public class Vertex { 

    private String label; 

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

    public boolean isVisited() { 
     return visited; 
    } 

    public String getLabel() { 
     return label; 
    } 

    public boolean equals(Object o) { 
     Vertex v = (Vertex)o; 
     return v.getLabel().equals(this.getLabel()); 
    } 
} 
+0

IIRC, концепция связывания каждого края с «противоположным» краем является обычным способом перехода сюда (хотя детали реализации могут отличаться). Можете ли вы более подробно описать «проблему»? Связано ли это с комментарием 'this обновляет только локальный список ...'? Это не должно быть так, так как кажется, что * ссылки * ребер используются повсюду, а новые ребра не создаются. Если вы сомневаетесь, вставьте метод 'printDebugInfo', который печатает все ребра и их потоки, и вызовите этот метод после каждого шага обновления (используя небольшой график, чтобы вы могли легко проверить вывод с помощью ручки +). – Marco13

+0

Итак, для вас код это нормально? Это хорошая реализация? Прямо сейчас, если я запускаю программу, это никогда не заканчивается. Однако я сделаю то, что вы говорите. PS: sei italiano? – user3075478

+0

С первого взгляда код выглядит нормально, но я не пробовал его или не читал в * all * detail - я просто хотел узнать, в чем проблема. Можно было бы рассмотреть возможность разделения структуры графика и алгоритма, но рекомендации, подобные этому, скорее подходят для http://codereview.stackexchange.com/. Но есть одна потенциальная проблема ... – Marco13

ответ

0

Хотя, строго говоря, этот вопрос может быть рассмотрен как "Off тема" (так как вы в основном ищете для отладки справки), это один из ваших первых вопросов, поэтому некоторые общие советы:


Когда вы публикуете здесь вопрос, подумайте, что здесь люди добровольцы. Сделать это легко для ответа на вопрос. В этом конкретном случае: вы должны создать MCVE, чтобы люди могли быстро скопировать & вставьте свой код (желательно в один блок кода) и без каких-либо усилий запустите программу. Например, вы должны включать в себя тестовый класс, Inculding метод main, как этот:

public class FFTest 
{ 
    /** 
    *  B---D 
    * /\/\ 
    * A . F 
    * \/\/
    *  C---E 
    */ 
    public static void main(String[] args) throws Exception 
    { 
     FF ff = new FF(); 

     Vertex vA = new Vertex("A"); 
     Vertex vB = new Vertex("B"); 
     Vertex vC = new Vertex("C"); 
     Vertex vD = new Vertex("D"); 
     Vertex vE = new Vertex("E"); 
     Vertex vF = new Vertex("F"); 
     ff.insertNode(vA); 
     ff.insertNode(vB); 
     ff.insertNode(vC); 
     ff.insertNode(vD); 
     ff.insertNode(vE); 
     ff.insertNode(vF); 

     ff.insertEdge(vA, vB, 3.0f); 
     ff.insertEdge(vA, vC, 2.0f); 
     ff.insertEdge(vB, vD, 1.0f); 
     ff.insertEdge(vB, vE, 4.0f); 
     ff.insertEdge(vC, vD, 2.0f); 
     ff.insertEdge(vC, vE, 1.0f); 
     ff.insertEdge(vD, vF, 2.0f); 
     ff.insertEdge(vE, vF, 1.0f); 

     float result = ff.fordFulkerson(vA, vF); 
     System.out.println(result); 
    } 
} 

(! Вы должны уже создали такой тестовый класс, когда вы пишете вопрос, так или иначе)


Вы должны четко указать, что вы являетесь NOT, используя StackOverflow как «волшебную машину для решения проблем». В этом случае: я уже упоминал, что вы должны включить отладочные выходы. Если вы расширили свой FF класс с методами, как это ....

private static void printPath(List<Edge> path) 
{ 
    System.out.println("Path: "); 
    for (int i=0; i<path.size(); i++) 
    { 
     Edge e = path.get(i); 
     System.out.println(
      "Edge "+e+ 
      " flow "+e.getFlow()+ 
      " cap "+e.getCapacity()); 
    } 
} 

, который можно назвать в основном цикле, как это:

while(bfs(source, destination, path)) { 
     ... 
     System.out.println("Before updating with "+minCap); 
     printPath(path); 

     // updates the maxFlow 
     .... 

     System.out.println("After updating with "+minCap); 
     printPath(path); 

     ... 
    } 

тогда вы уже заметили, главная проблема код: ...


bfs метод не так! Вы не правильно восстановили путь , который привел вас к целевой вершине. Вместо этого вы добавляете каждую посещенную вершину к пути. Вы должны отслеживать, какой край использовался для достижения определенного узла, и когда вы достигли вершины назначения, вам нужно вернуться назад.

Быстрый и грязный подход может примерно выглядеть следующим образом (!).

private boolean bfs(Vertex source, Vertex destination, List<Edge> path) { 
    Queue<Vertex> queue = new LinkedList<Vertex>(); 
    List<Vertex> visited = new ArrayList<Vertex>(); // list of visited vertexes 
    queue.add(source); 
    visited.add(source); 
    Map<Vertex, Edge> predecessorEdges = new HashMap<Vertex, Edge>(); 
    while(!queue.isEmpty()) { 
     Vertex d = queue.remove(); 
     if(!d.equals(destination)) { 
      ArrayList<Edge> d_outgoingEdges = outgoingEdges.get(d); 
      for(Edge e : d_outgoingEdges) { 
       if(e.getCapacity() - e.getFlow() > 0) { // there is still available flow 
        Vertex u = e.getDestination(); 
        if(!visited.contains(u)) { 
         visited.add(u); 
         queue.add(u); 
         predecessorEdges.put(u, e); 
        } 
       } 
      } 
     } 
     else 
     { 
      constructPath(destination, predecessorEdges, path); 
      return true; 
     } 
    } 
    return false; 
} 

private void constructPath(Vertex destination, 
    Map<Vertex, Edge> predecessorEdges, List<Edge> path) 
{ 
    Vertex v = destination; 
    while (true) 
    { 
     Edge e = predecessorEdges.get(v); 
     if (e == null) 
     { 
      return; 
     } 
     path.add(0, e); 
     v = e.getSource(); 
    } 
} 

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


Дальнейшие замечания:

Всякий раз, когда вы переопределить метод equals, вы должны переопределить метод hashCode. Некоторые правила применимы здесь, вы должны определенно относятся к documentation of Object#equals и Object#hashCode.

Часто полезно дополнительно переопределить метод toString, чтобы вы могли легко распечатать объекты на консоли.

В вашем случае, эти методы могут быть реализованы, как это, для Vertex

@Override 
public int hashCode() 
{ 
    return getLabel().hashCode(); 
} 

@Override 
public boolean equals(Object o) { 
    Vertex v = (Vertex)o; 
    return v.getLabel().equals(this.getLabel()); 
} 

@Override 
public String toString() 
{ 
    return getLabel(); 
} 

И Edge

@Override 
public String toString() 
{ 
    return "("+getSource()+","+getDestination()+")"; 
} 

@Override 
public int hashCode() 
{ 
    return source.hashCode()^destination.hashCode(); 
} 

@Override 
public boolean equals(Object o) { 
    Edge e = (Edge)o; 
    return e.getSource().equals(this.getSource()) &&  
     e.getDestination().equals(this.getDestination()); 
} 

Мощность кромок float значения, поэтому результирующий поток должен также значение float.

С изменениями, упомянутыми выше, программа запускает и печатает «правдоподобный» результат. У меня НЕ проверял его для правильности. Но это ваша задача, и теперь это должно быть намного проще.


P.S: Нет, io sono tedesco.

+0

Спасибо большое! Мой следующий вопрос будет лучше этого, я обещаю.Теперь он возвращает поток, неправильный поток, но это что-то. Я использовал ваш меток печати для отладки, и кажется, что поток на краю может превысить его емкость! – user3075478

+0

@ user3075478 Метод «узких мест» использует только * емкость *. Но он должен вернуть минимальный «поток мощности» всех ребер. (Части мощности уже могут быть «заняты» определенным потоком, и это пока не рассматривается). Но вы, вероятно, это выясните. – Marco13

+0

Я могу это исправить;) Мне нужно сделать этот алгоритм как можно быстрее, потому что он должен работать с графами VERY BIG. Большое спасибо! – user3075478

 Смежные вопросы

  • Нет связанных вопросов^_^