1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【最大流・最小カット】FordFulkerson法を実装してみた【C++】

Posted at

Ford-Fulkerson法

超ざっくりいうと、フローネットワークの最大フローを求めることができるアルゴリズムです。
この最大フローは最小カットと等しいという定理があるため、最大フローおよび最小カットを求めるアルゴリズムでもあります。
二部マッチングも最大流の問題に帰着できることがあり、そこでもフォードファルカーソン法は用いられます。

計算量

頂点数をM、最大流量をFとおきます。
頂点sからtへのDFSによる経路の検出に O(M) かかります。
また、一回のフローで総流量は1以上であるので、経路の探索を行う回数自体は高々 O(F) です。
よって、O(FM) となります。

典型的な問題を解いてみる

鉄則本a68 をフォードファルカーソン法で解いてみました。
実装にあたって、FordFulkersonというclassを作り、そのメソッドに辺の生成と残余ネットワークの更新を行う機能を付けました。
実際に使うときはffというインスタンスを生成した場合、

 ff.init(n);           //グラフの初期化
 ff.add_edge(a, b, c); //from, to, costで辺を生成
 ff.maxFlow(0, n-1)    //答えを取得

の三つの関数だけで済みます。

それではコードを以下に示します。

C++での実装例

FordFulkerson.cpp
/*FORDFULKERSON*/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define rep(i, a, b) for (int i = a; i < b; i++)
#define INF (1e9 + 1)

//辺の構造体
struct Edge {
    //rev: toから行ける頂点のうち、toから見てfromが何番目に位置するか
    //G[from].size() == rev  
    int rev, from, to, cap;
};

//フォードファルカーソン法
class FordFulkerson {
public:
    vector<vector<Edge>> G;
    vector<bool> visited;
    // 頂点数 n の残余グラフを用意
    int size = 0;
    void init(int n) {
        G.resize(n);
        visited.resize(n);
        size = n;
    }
    /*
        頂点 u -> v について 上限 cost の辺を追加
        コスト0の逆辺も張る
    */ 
    void add_edge(int u, int v, int cost) {
        int u_vID = G[u].size(); // 現時点での G[u] の要素数 = uからみたvのindex
        int v_uID = G[v].size(); // 現時点での G[v] の要素数 = vからみたuのindex
        G[u].emplace_back(Edge{v_uID, u, v, cost}); //<u,v>の逆辺<v,u>はG[u][v_uID]
        G[v].emplace_back(Edge{u_vID, v, u, 0}); //逆辺は追加時はコスト0!!
    }
    /*  
        深さ優先探索(F はスタートした頂点からposに到達する過程での
     "残余グラフの辺の容量" の最小値)
        goalまでの往路は頂点を記録しながらs->tまでに共通して流せる容量
                       = s->tまでの容量の最小値を取得
        復路はs->tまでの容量の最小値を使って残余ネットワークのコストを更新
        返り値: 流したフローの量
    */
    int dfs(int pos, int goal, int F) {
        if (pos == goal) return F; //ゴールに到着したら流す
        visited[pos] = true; //訪れた頂点を記録
    
        // G[pos]に隣接する頂点を探索
        for(auto &e : G[pos]){
            //容量0の辺や訪問済みの頂点は無視
            if (e.cap == 0 or visited[e.to]) continue;
            // 再帰で目的地までのパスを探す
            int flow = dfs(e.to, goal, min(F, e.cap));
            // 残余ネットワークの更新
            // フローを流せる場合、残余グラフの容量をflowだけ増減させる
            if (flow > 0) {
                e.cap -= flow;              //u->vの辺を減少
                G[e.to][e.rev].cap += flow; //v->uの辺を増加
                return flow;
            }
        }
        return 0;
    }
 
    // 頂点sから頂点tまでの最大フローの総流量を返す
    int maxFlow(int s, int t) {
        int totalFlow = 0;
        while (true) {
      //s->tに探索する前に記録した頂点をリセット
            visited.assign(size, false);
            int F = dfs(s, t, INF); //s->tへの流量を取得
            //フローを流せなくなったら終了
            if (F == 0) break;
            totalFlow += F;
        }
        return totalFlow;
    }
};

//グローバル変数で管理
int n, m;
int a,b,c;

//FordFulkersonのインスタンスff
FordFulkerson ff;
 
int main() {
    // 入力
    cin >> n >> m;
    //グラフの作成
    ff.init(n);
    rep(i,0,m){
        cin >> a >> b >> c;
        a--; b--;
        ff.add_edge(a, b, c); //from, to, costで辺を生成
    }
    //頂点0->n-1までの最大流の出力
    cout << ff.maxFlow(0, n-1) << endl;
    return 0;
}

このようになります。

解けました!

image.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?