Algorithm/그래프

Ford-Fulkerson algorithm

jhg0406 2020. 2. 10. 17:55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//네트워크 유량 문제를 해결하는 포드-폴커슨 알고리즘의 구현
 
const int INF = 987654321;
int V;
//capacity[u][v] : u에서 v로 보낼수 있는 용량
//flow[u][v] : u에서 v로 흘러가는 유량(반대 방향인 경우 음수)
int capacity[MAX_V][MAX_V], flow[MAX_V][MAX_V];
//flow[][]를 계산하고 총 유량을 반환한다
int networkFlow(int source, int sink)
{
    //flow를 0으로 초기화한다
    memset(flow, 0sizeof(flow));
    int totalFlow = 0;
    while(true)
    {
        //너비 우선 탐색으로 증가 경로를 찾는다
        vector<int> parent(MAX_V, -1);
        queue<int> q;
        parent[source] = source;
        q.push(source);
        while(!q.empty() && parent[sink] == -1)
        {
            int here = q.front(); q.pop();
            for(int there = 0; there < V; ++there)
                //잔여 용량이 남아 있는 간선을 따라 탐색한다.
                if(capacity[here][there] - flow[here][there] > 0 && parent[there] == -1)
                {
                    q.push(there);
                    parent[there] = here;
                }
        }
        //증가 경로가 없으면 종료한다
        if(parent[sink] == -1break;
        //증가 경로를 통해 유량을 얼마나 보낼지 결정한다
        int amount = INF;
        for(int p = sink; p != source; p = parent[p])
            amount = min(capacity[parent[p]][p] - flow[parent[p]][p], amount);
        //증가 경로를 통해 유량을 보낸다
        for(int p = sink; p != source; p = parent[p])
        {
            flow[parent[p]][p] += amount;
            flow[p][parent[p]] -= amount;
        }
        totalFlow += amount;
    }
    return totalFlow;
}
cs

 

 

 

 

 

Ford-Fulkerson algorithm

네트워크 유량 알고리즘

증가 경로가 더이상 존재하지 않을 때까지 증가 경로를 찾고, 보낼 수 있는 최대 유량을 해당 경로를 따라 보내는 작업을 반복합니다.

증가경로는 잔여 용량이 남은 간선들만을 이용하는 BFS를 이옹해 찾습니다.

SOURCE -> SINK 의 경로를 찾았다면, 각 간선을 검사하면서 그중 최소 잔여 용량을 찾습니다. 이것이 이 경로를 따라 보낼 수 있는 최대 유량입니다.

그 후에는 각 간선의 유량을 갱신하는데, 유량의 대칭성을 유지하기 위해 한 방향의 유량을 늘리면 다른 방향의 유량은 줄여줍니다!

 

 

 

 

 

 

Min-cut Max-flow Theorem

cut : source와 sink가 다른 집합에 속하도록 그래프의 정점들을 두 개의 집합으로 나눈것

 

source가 속한 집합 : S

sink가 속한 집합   : T

컷 S,T의 용량      : S->T로 가는 간선들의 용량 합

컷 S,T의 유량      : S->T로 실제로 보내는 총 유량

 

 

속성

1. 컷의 유량은 source->sink로 가는 총 유량과 같다

2. 컷의 유량은 용량과 같거나 더 작다

 

 

네트워크에 용량과 유량이 같은 컷 S`, T`가 존재하면, S`, T`는 항상 최소 컷이며, 현재 소스에서 싱크로 보내는 유량은 네트워크의 최대 유량입니다.

 

 

 

 

 

 

인접 리스트로 Ford-Fulkerson algorithm 구현하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//인접 리스트로 포드-폴커슨 알고리즘 구현하기
 
//간선의 정보를 나타내는 구조체
struct Edge
{
    int target, capacity, flow;
    //역방향 간선의 포인터
    Edge* reverse;
    //이 간선의 잔여 용량을 계산한다
    int residualCapacity() const
    {
        return capacity - flow;
    }
    //이 간선을 따라 용량 amt를 보낸다
    void push(int amt)
    {
        flow += amt;
        reverse->flow -= amt;
    }
};
//유량 네트워크의 인접 리스트
vector<Edge*> adj[MAX_V];
//u에서 v로 가는 간선을 추가한다
void addEdge(int u, int v, int capacity)
{
    Edge* uv = new Edge(), *vu = new Edge();
    //u에서 v로 가는 간선을 초기화한다
    uv->target = v;
    uv->capacity = capacity;
    uv->flow = 0;
    uv->reverse = vu;
    //v에서 u로 가는 간선을 초기화한다. 이 간선의 용량은 0이다
    vu->target = u;
    vu->capacity = 0;
    vu->flow = 0;
    vu->reverse = uv;
    adj[u].push_back(uv);
    adj[v].push_back(vu);
}
cs

각 단방향 간선 (u, v)마다 용량이 0인 유령간선(v, u)를 네트워크에 추가해 줘야 합니다.

이 유령 간선은 유량을 상쇄하는 데만 사용합니다.

각 간선의 유령간선에 빠르게 접근하기 위해 서로를 가르키는 포인터를 가지고 있습니다.

 

 

 

 

 

 

시간복잡도

f : 최대 유량

O(|E|f) 와 O(|V||E|^2)중 작은것