Workspace IndexAlgorithms › Day 12

Max Flow, Min Cut, and Matching TODO

Algorithms · Day 12 / 100 · A. Advanced Algorithms & Data Structures (Day 1-19)

Concept

A flow network is a directed graph where every edge has a capacity, and the max-flow problem asks for the largest flow that can be pushed from a source to a sink. The max-flow min-cut theorem says that the minimum total capacity over all cuts separating the source and sink equals the maximum flow exactly — a combinatorial instance of LP duality. The basic algorithmic approach is to repeatedly find an augmenting path in the residual graph and push flow along it; depending on how the path is chosen, this splits into Edmonds-Karp (shortest augmenting path) or Dinic's algorithm (level graph plus blocking flow). Bipartite matching reduces to a flow problem where every capacity is set to 1, and in that setting König's theorem — that the maximum matching size equals the minimum vertex cover size — follows as a special case of the min-cut theorem. Once cost is factored in, this generalizes to min-cost max-flow (MCMF), the general form of resource-to-demand allocation.

Whenever a "who gets what, how much" question comes up — scheduling, order allocation, node-to-shard assignment — it usually reduces to flow or matching, and getting the model wrong means ending up writing an exponential-time search instead.

Code & Formula

# Day 12: 최대 유량·최소 컷과 매칭 — Edmonds-Karp로 최대 유량을 구하고 최소 컷을 복원
# BFS로 최단 증가 경로를 찾아 유량을 밀어 넣고, 잔여 그래프에서 도달 가능한 집합이 곧 최소 컷이다.

from collections import defaultdict, deque

def edmonds_karp(capacity, source, sink):
    graph = defaultdict(dict)
    for (u, v), cap in capacity.items():
        graph[u][v] = graph[u].get(v, 0) + cap
        graph[v].setdefault(u, 0)

    def bfs_path():
        parent = {source: None}
        queue = deque([source])
        while queue:
            u = queue.popleft()
            if u == sink:
                break
            for v, cap in graph[u].items():
                if cap > 0 and v not in parent:
                    parent[v] = u
                    queue.append(v)
        if sink not in parent:
            return None
        path, v = [], sink
        while parent[v] is not None:
            path.append((parent[v], v))
            v = parent[v]
        return list(reversed(path))

    flow = 0
    while True:
        path = bfs_path()
        if path is None:
            break
        path_flow = min(graph[u][v] for u, v in path)
        for u, v in path:
            graph[u][v] -= path_flow
            graph[v][u] += path_flow
        flow += path_flow

    reachable, queue = {source}, deque([source])
    while queue:
        u = queue.popleft()
        for v, cap in graph[u].items():
            if cap > 0 and v not in reachable:
                reachable.add(v); queue.append(v)
    min_cut = [(u, v) for (u, v) in capacity if u in reachable and v not in reachable]
    return flow, min_cut

capacity = {
    ("S", "A"): 3, ("S", "B"): 2,
    ("A", "B"): 1, ("A", "T"): 2,
    ("B", "T"): 3,
}
max_flow, min_cut = edmonds_karp(capacity, "S", "T")
print("최대 유량 =", max_flow)
print("최소 컷 간선 =", min_cut, "(용량 합 =", sum(capacity[e] for e in min_cut), ")")

Exercise

Implement bipartite matching using Dinic's algorithm, then, for the same input, reconstruct the minimum vertex cover from the minimum cut and verify König's theorem in code.

Practical Connection

Splitting multiple orders across multiple liquidity sources for fills, or netting a large set of debt-credit relationships at settlement to reduce the number of on-chain transfers, both fall out naturally as min-cost flow models.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

최대 유량·최소 컷과 매칭 TODO

Algorithms · Day 12 / 100 · A. 고급 알고리즘·자료구조 (Day 1–19)

자원 배분 문제의 원형

개념

유량 네트워크는 각 간선에 용량이 있는 방향 그래프이고, 소스에서 싱크로 보낼 수 있는 최대 유량을 구하는 문제가 최대 유량 문제다. 최대 유량-최소 컷 정리는 소스와 싱크를 분리하는 컷 중 용량 합이 최소인 값이 최대 유량과 정확히 같다고 말하며, 이는 선형계획법 쌍대성의 조합론적 사례다. 알고리즘은 잔여 그래프(residual graph)에서 증가 경로를 찾아 유량을 밀어 넣는 방식이 기본이고, 경로 선택 전략에 따라 Edmonds-Karp(최단 증가 경로)나 Dinic(레벨 그래프 + 블로킹 유량)으로 나뉜다. 이분 매칭은 모든 용량을 1로 둔 유량 문제로 환원되며, 이때 최대 매칭 크기가 최소 정점 덮개 크기와 같다는 König 정리가 최소 컷 정리의 특수형으로 따라 나온다. 비용까지 고려하면 최소 비용 최대 유량(MCMF)으로 확장되어, 자원-수요 배분의 일반형이 된다.

스케줄링, 주문 배정, 노드-샤드 할당처럼 "누구에게 무엇을 얼마나" 문제가 나오면 대부분 유량 또는 매칭으로 환원되며, 잘못 모델링하면 지수 시간 탐색을 짜게 된다.

코드 · 수식

# Day 12: 최대 유량·최소 컷과 매칭 — Edmonds-Karp로 최대 유량을 구하고 최소 컷을 복원
# BFS로 최단 증가 경로를 찾아 유량을 밀어 넣고, 잔여 그래프에서 도달 가능한 집합이 곧 최소 컷이다.

from collections import defaultdict, deque

def edmonds_karp(capacity, source, sink):
    graph = defaultdict(dict)
    for (u, v), cap in capacity.items():
        graph[u][v] = graph[u].get(v, 0) + cap
        graph[v].setdefault(u, 0)

    def bfs_path():
        parent = {source: None}
        queue = deque([source])
        while queue:
            u = queue.popleft()
            if u == sink:
                break
            for v, cap in graph[u].items():
                if cap > 0 and v not in parent:
                    parent[v] = u
                    queue.append(v)
        if sink not in parent:
            return None
        path, v = [], sink
        while parent[v] is not None:
            path.append((parent[v], v))
            v = parent[v]
        return list(reversed(path))

    flow = 0
    while True:
        path = bfs_path()
        if path is None:
            break
        path_flow = min(graph[u][v] for u, v in path)
        for u, v in path:
            graph[u][v] -= path_flow
            graph[v][u] += path_flow
        flow += path_flow

    reachable, queue = {source}, deque([source])
    while queue:
        u = queue.popleft()
        for v, cap in graph[u].items():
            if cap > 0 and v not in reachable:
                reachable.add(v); queue.append(v)
    min_cut = [(u, v) for (u, v) in capacity if u in reachable and v not in reachable]
    return flow, min_cut

capacity = {
    ("S", "A"): 3, ("S", "B"): 2,
    ("A", "B"): 1, ("A", "T"): 2,
    ("B", "T"): 3,
}
max_flow, min_cut = edmonds_karp(capacity, "S", "T")
print("최대 유량 =", max_flow)
print("최소 컷 간선 =", min_cut, "(용량 합 =", sum(capacity[e] for e in min_cut), ")")

연습

이분 매칭을 Dinic으로 직접 구현하고, 같은 입력에 대해 최소 정점 덮개를 최소 컷에서 복원해 König 정리를 코드로 확인해 볼 것.

실무 · Verex 연결

여러 주문을 여러 유동성 소스에 나눠 체결하거나, 정산 시 다수 채무-채권 관계를 상계(netting)해 온체인 전송 횟수를 줄이는 문제는 최소 비용 유량으로 자연스럽게 모델링된다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 11. 위상정렬·DAG 스케줄링13. 선형계획과 쌍대성 직관 →