Возникли проблемы с пониманием и реализацией алгоритма Форда Фулкерсона.

Я пытаюсь реализовать алгоритм Форда Фулкерсона на Java. Пока у меня есть график с узлами и ребрами. Узлы содержат строку идентификатора и список ребер adjacencyList. Ребра содержат емкость и узел, к которому она ведет.

Я пытаюсь понять псевдокод на странице Википедии и как его реализовать (я использую Java). Это то, что я понимаю до сих пор:

  1. Сначала я установил поток всех ребер в графе равным нулю. (Какой хороший способ представить поток? Непосредственно в ребрах моего графа в качестве переменной?)

  2. Второй шаг — создать остаточный граф, представляющий собой сеть, в которой ребра имеют остаточную пропускную способность: пропускная способность — поток (соответствующих ребер в «нормальном графе»). Затем вы находите путь от источника к приемнику, используя этот остаточный граф, и находите минимальную пропускную способность на этом пути. (Здесь все становится действительно сложно, должен ли я создать совершенно новый граф для остаточного графа или я должен каким-то образом представить его в исходном графе? Как лучше всего?)

Шаг 2 повторяется до тех пор, пока путь не будет найден, но каждый раз, когда он будет найден, вы выполняете шаги 3 и 4:

  1. Для каждого ребра вдоль пути вы добавляете минимальное значение, найденное на шаге 2.

  2. Для каждого ребра в противоположном направлении пути вы вычитаете минимальное значение, найденное на шаге 2.

Шаги 3 и 4 озадачивают меня, так как мне кажется, что прибавлять в одном направлении — это то же самое, что и вычитать в противоположном. Эти сложения и вычитания делаются в графе правильно, а не в остаточном графе?

Был бы очень признателен за помощь, я уже пару часов пытаюсь обдумать это, но я просто не могу этого понять.


person Alex    schedule 20.05.2013    source источник
comment
Извините, если это не слишком полезно, но я помню, как несколько часов перечитывал доказательство для Форда-Фалкерсона в своих заметках, прежде чем наконец получил его. Суть доказательства довольно проста, но я помню детали с отрицательными потоками и другими вещами, которые, казалось бы, никогда не имели смысла. Википедия обычно не слишком хороша для доказательств, потому что они всегда опускают детали из соображений краткости. Здесь есть полное доказательство (отсутствуют лишь некоторые детали), но вам может понадобиться выучить много дополнительного жаргона, описанного ранее в pdf: cs.cmu.edu/~avrim/451f11/lectures/lect1025.pdf   -  person rliu    schedule 21.05.2013


Ответы (2)


Вероятно, вам следует сначала реализовать это с плотным графом. Таким образом, вы можете предположить, что между каждой парой различных вершин есть ребро в каждом направлении. Вы можете представить функции на ребрах как |V| х |В| матрицы. В частности, объявления емкости и потока довольно просты:

int[][] cap = new int[numverts][numverts];
int[][] flow = new int[numverts][numverts];

Один полезный прием состоит в том, чтобы представить поток k единиц вдоль ребра vw как поток a от v к w и поток -a от w к v. Таким образом, вам не нужно беспокоиться о том, толкает ли каждый край вашего увеличивающего пути больший поток вперед или меньший поток назад. Если вы сделаете это, вы можете вычислить остаточную емкость вдоль vw просто cap[v][w] - flow[v][w].

При таком представлении поиск увеличивающего пути становится поиском в ширину в плотном графе, где есть ребро от v до w точно при cap[v][w] > flow[v][w]. Это довольно просто реализовано.

Поскольку вы используете java, вы должны помнить о накладных расходах на каждый объект. Описанная вами структура Edge содержит не только два целых числа (или указателя) и два двойных числа, но также такие вещи, как информация GC, указатель класса и монитор. Это несколько десятков дополнительных байтов, легко удваивающих или утраивающих размер вашего объекта.

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

int[] from = new int[numverts+1];
int[] to = new int[numedges];

Отсортируйте ребра по вершине «из». ith запись массива from — это индекс первого ребра, вершина которого «из» является ith вершиной или более поздней. В конце есть одна дополнительная запись, и вы должны установить ее на numedges; это удобно, когда вы хотите перебрать все ребра, выходящие из данной вершины.

Поскольку вы работаете с потоком, вы также захотите сохранить обратные ребра, поэтому создайте

int[] rev = new int[numedges];

для хранения индекса ребра реверса каждого ребра. Теперь вы можете представлять произвольные функции, скажем, cap и flow, на ребрах следующим образом:

int[] cap = new int[numedges];
int[] flow = new int[numedges];

Так что вопрос о том, хранить ли эти атрибуты в структуре Edge, спорный, потому что структура Edge исчезла.

person tmyklebu    schedule 20.05.2013
comment
хорошо, я знаю, что этот вопрос старый, но я все же хотел бы спросить: я действительно не понимаю, как мне использовать int[] rev - или какими значениями я бы его заполнил? - person platzhersh; 22.11.2014
comment
@platzhersh: Если в графе есть ребро ab, вы сохраняете ребро, направленное a->b, и ребро, направленное b->a, отдельно. Если a->b имеет индекс i, а b->a имеет индекс j, то у вас должны быть rev[i] = j и rev[j] = i. - person tmyklebu; 22.11.2014

Вот реализация метода Форда-Фалкерсона с использованием поиска в глубину (DFS) с использованием списка смежности для хранения емкостей. Сложность использования списка смежности в отличие от матрицы смежности с Фордом-Фалкерсоном заключается в том, что вам нужно самостоятельно отслеживать остаточные ребра. Для упрощения я создал метод addEdge, который обрабатывает остаточные края. Однако преимуществом использования списка смежности в отличие от матрицы смежности является то, что временная сложность снижается с O(fV2) до O(fE)< /strong> что может иметь значение.

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

Код взят из моего репозитория алгоритмов. Не стесняйтесь проверить это, там также есть версия этого алгоритма с матрицей смежности.

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

}
person will.fiset    schedule 02.11.2017