RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Minimum Cut

p
"""
Minimum cut of a flow network via the Ford-Fulkerson algorithm.

The max-flow min-cut theorem says the value of a maximum flow from the source to
the sink equals the total capacity of the edges in a minimum s-t cut -- the
cheapest set of edges whose removal disconnects the sink from the source.  This
module finds those cut edges: it runs Ford-Fulkerson to build the residual
graph, then reports every original edge that goes from a vertex still reachable
from the source to a vertex that is not.

Reference: https://en.wikipedia.org/wiki/Minimum_cut
See also: https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem
"""

test_graph = [
    [0, 16, 13, 0, 0, 0],
    [0, 0, 10, 12, 0, 0],
    [0, 4, 0, 0, 14, 0],
    [0, 0, 9, 0, 0, 20],
    [0, 0, 0, 7, 0, 4],
    [0, 0, 0, 0, 0, 0],
]


def bfs(graph: list[list[int]], source: int, sink: int, parent: list[int]) -> bool:
    """
    Return True if the ``sink`` is reachable from the ``source`` in the
    residual ``graph``, recording the traversal tree in ``parent``.

    >>> bfs(test_graph, 0, 5, [-1] * 6)
    True
    >>> bfs([[0, 0], [0, 0]], 0, 1, [-1, -1])
    False
    """
    visited = [False] * len(graph)
    queue = [source]
    visited[source] = True

    while queue:
        node = queue.pop(0)
        for neighbor in range(len(graph[node])):
            if visited[neighbor] is False and graph[node][neighbor] > 0:
                queue.append(neighbor)
                visited[neighbor] = True
                parent[neighbor] = node

    return visited[sink]


def mincut(graph: list[list[int]], source: int, sink: int) -> list[tuple[int, int]]:
    """
    Return the edges of a minimum s-t cut as ``(from, to)`` tuples.

    The input ``graph`` is an adjacency matrix of capacities and is left
    unchanged (the algorithm works on an internal copy).

    >>> mincut(test_graph, source=0, sink=5)
    [(1, 3), (4, 3), (4, 5)]

    The capacities of the cut edges sum to the maximum flow (23 here):

    >>> sum(test_graph[u][v] for u, v in mincut(test_graph, 0, 5))
    23

    A single saturated edge is its own minimum cut:

    >>> mincut([[0, 7], [0, 0]], source=0, sink=1)
    [(0, 1)]
    """
    residual = [row[:] for row in graph]  # work on a copy; keep the input intact
    parent = [-1] * (len(residual))
    res = []
    while bfs(residual, source, sink, parent):
        path_flow = max(max(row) for row in residual)
        s = sink

        while s != source:
            # Find the minimum residual capacity along the augmenting path.
            path_flow = min(path_flow, residual[parent[s]][s])
            s = parent[s]

        v = sink
        while v != source:
            u = parent[v]
            residual[u][v] -= path_flow
            residual[v][u] += path_flow
            v = parent[v]

    for i in range(len(graph)):
        for j in range(len(graph[0])):
            if graph[i][j] > 0 and residual[i][j] == 0:
                res.append((i, j))

    return res


if __name__ == "__main__":
    from doctest import testmod

    testmod()
    print(mincut(test_graph, source=0, sink=5))