我正在尝试用Java实现Ford Fulkerson算法。到目前为止,我有一个包含Nodes和Edges的图表。节点包含ID字符串和边缘的adjacencyList。边缘包含容量和它导致的节点。
我正在尝试理解维基百科页面上的psudo代码以及如何实现它(我正在使用Java)。这是我到目前为止所理解的:
首先,我将图形中所有边缘的流量设置为零。(表示流量的好方法是什么?直接在图形的边缘作为变量?)
第二步是创建残差图,这是一个边缘具有剩余容量的网络:容量 - 流量(“法线图”中相应边缘的流量)。然后使用此残差图找到从源到接收器的路径,并找到沿此路径的最小容量。 (这是事情变得非常棘手的地方,我应该为残差图创建一个全新的图形,还是应该在原始图形中以某种方式表示它?最好的方法是什么?)
重复步骤2,直到找不到路径,但每次找到你做的第3步和第4步:
对于沿路径的每条边,您可以添加步骤2中找到的最小值。
对于路径相反方向的每条边,您将减去步骤2中找到的最小值。
第3步和第4步让我感到困惑,因为我觉得在一个方向上添加它是相同的,因为它是相反的减去。这些加法和减法是在右图中进行的,而不是残差图?
真的很感激一些帮助,我一直试图绕过这个问题几个小时,但我似乎无法理解它。
答案 0 :(得分:2)
您应该首先使用密集图来实现它。这样,您可以假设每对不同顶点之间的每个方向都有一条边。您可以将边上的函数表示为| V | x | V |矩阵。特别是,容量和流量的声明非常简单:
int[][] cap = new int[numverts][numverts];
int[][] flow = new int[numverts][numverts];
一个有用的技巧是沿着边k
表示vw
单位的流量,作为a
从v
到w
的流量,以及从-a
到w
的{{1}}。这样,您不必担心增强路径的每个边缘是向前推动更多流动还是向后流动更少。如果您执行此操作,则只需v
即可vw
计算cap[v][w] - flow[v][w]
的剩余容量。
通过这种表示,找到一个扩充路径成为密集图中的广度优先搜索,其中v
到w
的边缘正好在cap[v][w] > flow[v][w]
时。这很简单。
由于你使用的是java,你应该注意它的每个对象的开销。您描述的Edge结构不仅包含两个整数(或指针)和两个双精度,还包含GC信息,klass指针和监视器等内容。这是几十个额外的字节,容易使对象的大小加倍或三倍。
当您使代码与稀疏图一起使用时,静态稀疏图的更好表示如下:
int[] from = new int[numverts+1];
int[] to = new int[numedges];
按“从”顶点对边进行排序。 i
数组的from
条目是第一条边的索引,其“from”顶点是i
顶点或更晚。最后有一个额外的条目,您应该将其设置为numedges
;当你想要遍历离开给定顶点的所有边时,它会派上用场。
由于你在做流程,你也想要存储后向边缘,所以有一个
int[] rev = new int[numedges];
存储每条边的反转的边索引。现在,您可以在边缘上表示任意函数,例如cap
和flow
,如下所示:
int[] cap = new int[numedges];
int[] flow = new int[numedges];
因此,是否将这些属性存储在Edge
结构中是没有意义的,因为Edge
结构已经消失。
答案 1 :(得分:2)
这是使用深度优先搜索(DFS)使用邻接列表来存储容量的Ford-Fulkerson方法的实现。使用邻接列表而不是与Ford-Fulkerson的邻接矩阵相比,棘手的部分是你需要自己跟踪剩余边缘。为简化起见,我创建了一个处理剩余边的'addEdge'方法。使用邻接列表而不是邻接矩阵的好处是时间复杂度从 O(fV 2 )减少到 O(fE)< / strong>这可能很重要。
在较高的层面上,大多数流算法的工作方式大致相同。他们所做的只是从源节点开始并使用一些技术(在下面的示例中进行深度优先搜索),他们找到了接收器(终端节点)的扩充路径。找到扩充路径后,您可以通过瓶颈值减少沿路径的每条边的容量,并将此值添加到残差图中。你重复这个程序,直到没有更多的增强路径可以找到它的宾果游戏!这些是您能够找到最大流量和最小切割所需的所有步骤。
代码来自我的algorithm repo。随意检查出来,那里还有一个邻接矩阵版本的算法。
/**
* An implementation of the Ford-Fulkerson (FF) method with a DFS
* as a method of finding augmenting paths. FF allows you to find
* the max flow through a directed graph and the min cut as a byproduct.
*
* Time Complexity: O(fE), where f is the max flow and E is the number of edges
*
* @author William Fiset
**/
import java.util.ArrayList;
import java.util.List;
public class FordFulkersonDfsSolverAdjacencyList {
public static class Edge {
public Edge residual;
public int to, capacity;
public final int originalCapacity;
public Edge(int to, int capacity) {
this.to = to;
this.capacity = capacity;
this.originalCapacity = capacity;
}
}
// Inputs
private int n, source, sink;
// Internal
private int visitedToken = 1;
private int[] visited;
private boolean solved;
// Outputs
private int maxFlow;
private boolean[] minCut;
private List<List<Edge>> graph;
/**
* Creates an instance of a flow network solver. Use the {@link #addEdge(int, int, int)}
* method to add edges to the graph.
*
* @param n - The number of nodes in the graph including source and sink nodes.
* @param source - The index of the source node, 0 <= source < n
* @param sink - The index of the source node, 0 <= sink < n
*/
public FordFulkersonDfsSolverAdjacencyList(int n, int source, int sink) {
this.n = n;
initializeGraph();
this.source = source;
this.sink = sink;
}
/**
* Adds a directed edge (and residual edge) to the flow graph.
*
* @param from - The index of the node the directed edge starts at.
* @param to - The index of the node the directed edge end at.
* @param capacity - The capacity of the edge.
*/
public void addEdge(int from, int to, int capacity) {
Edge e1 = new Edge(to, capacity);
Edge e2 = new Edge(from, 0);
e1.residual = e2;
e2.residual = e1;
graph.get(from).add(e1);
graph.get(to).add(e2);
}
/**
* Returns the graph after the solver has been executed. This allow you to
* inspect each edge's remaining {@link Edge#capacity} compared to the
* {@link Edge.originalCapacity} value. This is useful if you want to figure
* out which edges were used during the max flow.
*/
public List<List<Edge>> getGraph() {
solve();
return graph;
}
// Returns the maximum flow from the source to the sink.
public int getMaxFlow() {
solve();
return maxFlow;
}
// Returns the min-cut of this flow network in which the nodes on the "left side"
// of the cut with the source are marked as true and those on the "right side"
// of the cut with the sink are marked as false.
public boolean[] getMinCut() {
solve();
return minCut;
}
// Performs the Ford-Fulkerson method applying a depth first search as
// a means of finding an augmenting path. The input consists of a directed graph
// with specified capacities on the edges.
public void solve() {
if (solved) return;
maxFlow = 0;
visited = new int[n];
minCut = new boolean[n];
// Find max flow.
int flow;
do {
// Try to find an augmenting path from source to sink
flow = dfs(source, Integer.MAX_VALUE);
visitedToken++;
maxFlow += flow;
} while(flow != 0);
// Find min cut.
for(int i = 0; i < n; i++)
if (visited[i] == visitedToken-1)
minCut[i] = true;
solved = true;
}
private int dfs(int node, int flow) {
// At sink node, return augmented path flow.
if (node == sink) return flow;
List<Edge> edges = graph.get(node);
visited[node] = visitedToken;
for (Edge edge : edges) {
if (visited[edge.to] != visitedToken && edge.capacity > 0) {
// Update flow to be bottleneck
if (edge.capacity < flow) flow = edge.capacity;
int dfsFlow = dfs(edge.to, flow);
// Update edge capacities
if (dfsFlow > 0) {
Edge res = edge.residual;
edge.capacity -= dfsFlow;
res.capacity += dfsFlow;
return dfsFlow;
}
}
}
return 0;
}
// Construct an empty graph with n nodes including the source and sink nodes.
private void initializeGraph() {
graph = new ArrayList<>(n);
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
}
}