【问题标题】:Update the graph in Ford-Fulkerson更新 Ford-Fulkerson 中的图表
【发布时间】:2014-11-30 11:23:40
【问题描述】:

我正在实现 Ford-Fulkerson 算法,但在增强阶段后更新图形时遇到了一些问题。我猜我的数据结构并不容易。

为了表示图形,我使用这个:

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

也就是说,我在每个 Vertex 关联其输出边列表。

为了管理后向边,我为图中的每条边关联了一个“相反”边。

欢迎提出任何建议。

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

顶点

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

【问题讨论】:

  • IIRC,将每个边缘与“相反”边缘相关联的概念是这里的常用方法(尽管实现细节可能会有所不同)。你能更详细地描述“问题”吗?和this updates only the local list...的评论有关系吗?这不应该是这种情况,因为似乎到处都使用了边的references,并且没有创建新的边。有疑问,插入一个打印所有边及其流的printDebugInfo方法,并在每个更新步骤后调用此方法(使用小图,以便您可以轻松地用笔+纸验证输出)
  • 所以对你来说代码可以吗?这是一个很好的实现吗?现在,如果我启动程序,它永远不会结束。不过,我会照你说的做。 PS:sei italiano?
  • 乍一看,代码看起来没问题,但我没有尝试过,也没有详细阅读过 all - 我只是想知道实际的问题是什么。可以考虑将图结构和算法分开,但这样的建议更适合codereview.stackexchange.com。但是有一个潜在的问题......
  • 潜在问题:您似乎永远不会通过在所有节点上调用 node.setVisited(false) 来清除节点的“已访问”标志。如果它永远运行,调试输出(如上所述)可能已经有帮助。如果没有,请尝试创建一个stackoverflow.com/help/mcve(带有 main 方法和一个小示例图)。
  • 好的,我修改了 BFS。现在程序终止但返回 0... uhmmm。我会把更新的代码放上来。

标签: java graph ford-fulkerson


【解决方案1】:

虽然严格来说,这个问题可以被认为是“离题”(因为您主要是在寻找调试帮助),但这是您的第一个问题,所以一些一般性提示:


当您在此处发布问题时,请将此处的人视为志愿者。让他们轻松回答问题。在这种特殊情况下:您应该创建一个MCVE,以便人们可以快速复制和粘贴您的代码(最好是在单个代码块中)并毫不费力地运行程序。例如,您应该包含一个测试类,包括一个 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);
    }
}

(无论如何,你在写问题时应该已经创建了这样一个测试类!)


您应该明确表明您将 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();
    }
}

(你应该总是独立测试这样一个中心方法。你可以很容易地创建一个计算多条路径的小测试程序,你很快就会注意到这些路径是错误的 - 因此,福特 Fulkerson 根本无法正常工作)。


补充说明:

无论何时重写equals 方法,您还必须重写hashCode 方法。此处适用一些规则,您应该绝对参考documentation of Object#equalsObject#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。

【讨论】:

  • 非常感谢!我保证,我的下一个问题会比这个更好。现在它返回了一个流程,一个错误的流程,但就是这样。我用你的打印方法调试,似乎边缘的流量可以超过他的容量!
  • @user3075478 “瓶颈”方法仅使用容量。但它应该返回所有边的最小“容量流”。 (部分容量可能已经被某个流量“占用”了,目前还没有考虑到)。但你可能会明白这一点。
  • 我可以修复它;)我也需要使这个算法尽可能快,因为它必须适用于非常大的图。再次感谢!
  • 有些库已经提供了 FordFulkerson 的实现,可能还有其他 MaxFlow-Algorithms(对于大图甚至可能更有效!)。但这意味着“图书馆推荐”,这肯定对于 StackOverflow 来说是题外话......
  • 我需要自己实现,没有库;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-19
  • 2014-05-09
  • 1970-01-01
  • 1970-01-01
  • 2019-07-04
  • 2013-05-21
  • 2016-09-12
相关资源
最近更新 更多