[网络流]最大流算法 Dinic

来源:互联网 发布:hbase数据库设计原则 编辑:程序博客网 时间:2024/05/16 15:33
最近做了几道题,发现用Ek算法会超时,而事实上,Ek算法使用的机会并不多,更多的是用Dinic和ISAP算法。所以特地找了一段时间来学习、理解和编Dinic算法。

类似之前的储存方法,但稍作修改,代码如下:

struct Edge {    int from, to, cap, flow;};
这样储存的是一条边。在做题的过程中,发现一个技巧(也算是技巧吧),出现无向边时,不需要加两条边,而是直接将反向边的容量也改为cap,会减少很多多余的时间和空间。

插入边的过程与之前相同:

void AddEdge(int from, int to, int cap) {    edges.push_back((Edge) {        from, to, cap, 0    });    edges.push_back((Edge) {        to, from, 0, 0    });    m = edges.size();    G[from].push_back(m - 2);    G[to].push_back(m - 1);}

Dinic算法中,主要的操作过程如下:通过BFS构造层次图,然后DFS一次增广。

首先先来看看层次图。

在残量网络中,s到节点v的距离为d,那么d就是节点v的层次。只保留每个点到下一个层次的弧,也就是d(u) + 1 = d(v),的图,就是层次图。

层次图上的任意一条路径都是从s到层次1到层次2……直到t,可以发现,每一条这样的路径都是一条s-t最短路,这样求,不会出现走多余的边的情况。

如图,这样的一个图就是一个层次图。


从1开始,2和3都是层次1,4和5是层次2,6是层次3

在层次图上DFS求最大流自然很简单,就是不考虑反向边时能得到的最大流,多次增广后,重新计算层次图,发现s和t不连通,就退出。

它的时间复杂度不会很高,相对Ek会快很多,最多只会跑n-1次DFS,因为每次DFS之后s到t的路径至少会少一条,也就是最大距离至少会增加1,而每一次跑DFS最多会用nm的时间,每一次最多遍历每一个节点,每一个节点遍历它所链接的所有边,所以最终时间复杂度为O(N^2*M),相对Ek来说会快很多。

实际上,理论数值所对应的数据几乎不会出现,通常并不会想理论值说的这么慢,只会更快。

而且,对于它,还有一个很重要的优化,对于DFS来说,要保存一个“当前所有弧的最小残量”,如果等于0,必然是不能增广的,找到一条路径直接返回它的值即可,不然会出现多路增广不会退出的情况,还需要多加一次再算增广量,必然会很慢。

还有,当前弧优化,对于一次DFS中,可能会重复访问一个节点,虽然可能性比较小,这时候,已经走过的边自然是不需要再走,那么用一个数组记录上一次这个点走了哪些连接它的边,再次访问时不再重复走,那么就会更快。

下面为代码:

struct Dinic {    int n, m, s, t;    vector < Edge > edges;    vector < int > G[MAXN];    bool vis[MAXN];    int d[MAXN];    int cur[MAXN];    void AddEdge(int from, int to, int cap) {        edges.push_back((Edge) {            from, to, cap, 0        });        edges.push_back((Edge) {            to, from, 0, 0        });        m = edges.size();        G[from].push_back(m - 2);        G[to].push_back(m - 1);    }    bool BFS() {        memset(vis, 0, sizeof vis);        queue < int > Q;        Q.push(s);        d[s] = 0;        vis[s] = 1;        while(!Q.empty()) {            int x = Q.front();            Q.pop();            for(int i = 0; i < G[x].size(); ++i) {                Edge& e = edges[G[x][i]];                if(!vis[e.to] && e.cap > e.flow) { //如果未曾访问过,而且这一条边处于残量网络中,那么计算层次                    vis[e.to] = 1;                    d[e.to] = d[x] + 1;                    Q.push(e.to);                }            }        }        return vis[t];    }    int DFS(int x, int a) {        if(x == t || a == 0) { // 如果找到一条增广的路或无法继续增广,返回当前流量            return a;        }        int flow = 0, f;        for(int& i = cur[x]; i < G[x].size(); ++i) {            Edge& e = edges[G[x][i]];            if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) { // 如果处于同一个层次且这一条路径还能增广                e.flow += f;                edges[G[x][i] ^ 1].flow -= f;                flow += f;                a -= f;//增广 注意当前流量a要减f                if(a == 0) {//如果a=0,那么不能继续增广,返回上一条路                    break;                }            }        }        return flow;    }    int Maxflow(int s, int t) {        this -> s = s;        this -> t = t;        int flow = 0;        while(BFS()) { // 计算层次图 如果s-t不连通那么退出            memset(cur, 0, sizeof cur); // 初始化当前弧            flow += DFS(s, INF); // 计算最大流        }        return flow;    }} dinic;

当然,这里的DFS用的是递归实现的,但是如果Dinic依然会超时,那么可以尝试将DFS写成迭代的过程,会提高一些速度,但是也会相应地提高代码量,不如直接使用ISAP,不仅更快,而且代码稍微少一些。

0 0