帮我解释下网络流

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了帮我解释下网络流相关的知识,希望对你有一定的参考价值。

有关网络流的算法 帮我解释下
谢谢了

必须知识:最短路径问题
1.Dijkstra
适用于满足所有权系数大于等于0(lij≥0)的网络最短路问题,能求出起点v1到所有其他点vj的最短距离;
朴素的Dijkstra算法复杂度为O(N^2),堆实现的Dijkstra复杂度为O(NlogN).

2.bellman-ford
适用于有负权系数,但无负回路的有向或无向网络的最短路问题,能求出起点v1到所有其它点 vj的最短距离。bellman-ford算法复杂度为O(V*E)。

3.Floyed
适用于有负权系数,可以求出图上任意两点之间的最短路径。DP思想的算法,时间复杂度为O(N^3);
for ( k= 1; k<= n; k++)
for ( i= 1; i<= n; i++)
if (graph[i][k]!=INF)
for ( j= 1; j<= n; j++)
if (graph[k][j]!=INF && graph[i][k]+graph[k][j]< graph[i][j])
graph[i][j]= graph[i][k]+ graph[k][j];

NO.1 s-t最大流
两大类算法
1.增广路算法
Ford-Fulkerson算法: 残留网络中寻找增加路径
STEP0:置初始可行流。
STEP1:构造原网络的残量网络,在残量网络中找s-t有向路。如果没有,算法得到最大流结束。否则继续下一步。
STEP2:依据残量网络中的s-t有向路写出对应到原网络中的s-t增广路。对于增广路中的前向弧,置s(e)=u(e)- f(e)。对于反向弧,置s(e)=f(e) STEP3:计算crement=mins(e1),s(e2),…,s(ek)
STEP4:对于增广路中的前向弧,令f(e)=f(e)+crement;对于其中的反向弧,令f(e)=f(e)-crement,转STEP1。
关键点:寻找可增广路。决定了算法复杂度。
实现:Edmonds-Karp 通过采用了广度优先的搜索策略得以使其复杂度达到O(V*E^2)。

优化—> Dinic算法(*)
Dinic算法的思想是为了减少增广次数,建立一个辅助网络L,L与原网络G具有相同的节点数,但边上的容量有所不同,在L上进行增广,将增广后的流值回写到原网络上,再建立当前网络的辅助网络,如此反复,达到最大流。分层的目的是降低寻找增广路的代价。
算法的时间复杂度为O(V^2*E)。其中m为弧的数目,是多项式算法。邻接表表示图,空间复杂度为O(V+E)。

2.预流推进算法
一般性的push-relabel算法: 时间复杂度达到O(V^2*E)。(*)
relabel-to-front算法->改进
最高标号预流推进:时间复杂度O(V^2*sqrt(E))

NO2. 最小费用最大流
求解最小费用流的步骤和求最大流的步骤几乎完全一致,只是在步骤1时选一条非饱和路时,应选代价和最小的路,即最短路。
步骤1. 选定一条总的单位费用最小的路,即要给定最小费用的初始可行流,而不是包含边数最小的路。
步骤2. 不断重复求最大流的步骤来进行,直到没有饱和路存在为止。然后计算每个路的总费用。
和Edmonds-Karp标号算法几乎一样,因为这两种算法都使用宽度优先搜索来来寻找增广路径,所以复杂度也相同,都是O(V*E^2)。

连续最短路算法 + 线性规划对偶性优化的原始对偶算法(*)
传说中,没见过,据说复杂度是O(V^3)

NO3. 有上下届的最大流和最小流(通过添加点来进行转化)(*)

NO4. 相关图论算法
二分图最大匹配: 加s和t构造最大流
专用算法:hungary算法 O(M*N)

二分图最佳匹配: 加s和t构造最小费用最大流
专用算法:KM算法
朴素的实现方法,时间复杂度为O(n^4)
加上松弛函数O(n^3)

最小路径覆盖:
顶点数-二分图的最大匹配

s-t最小边割集:
最大流最小割定理:最小割等于最大流

普通最小边割集:
Stoer-Wagner Minimum Cut O(n^3)

二分图的最大独立集:
N - 二分图的最大匹配(POJ monthly)girls and boys
反证法证明
普通图的最大独立集是np问题。(*)
参考技术A 必须知识:最短路径问题
1.Dijkstra
适用于满足所有权系数大于等于0(lij≥0)的网络最短路问题,能求出起点v1到所有其他点vj的最短距离;
朴素的Dijkstra算法复杂度为O(N^2),堆实现的Dijkstra复杂度为O(NlogN).
2.bellman-ford
适用于有负权系数,但无负回路的有向或无向网络的最短路问题,能求出起点v1到所有其它点
vj的最短距离。bellman-ford算法复杂度为O(V*E)。
3.Floyed
适用于有负权系数,可以求出图上任意两点之间的最短路径。DP思想的算法,时间复杂度为O(N^3);
for
(
k=
1;
k<=
n;
k++)
for
(
i=
1;
i<=
n;
i++)
if
(graph[i][k]!=INF)
for
(
j=
1;
j<=
n;
j++)
if
(graph[k][j]!=INF
&&
graph[i][k]+graph[k][j]<
graph[i][j])
graph[i][j]=
graph[i][k]+
graph[k][j];
NO.1
s-t最大流
两大类算法
1.增广路算法
Ford-Fulkerson算法:
残留网络中寻找增加路径
STEP0:置初始可行流。
STEP1:构造原网络的残量网络,在残量网络中找s-t有向路。如果没有,算法得到最大流结束。否则继续下一步。
STEP2:依据残量网络中的s-t有向路写出对应到原网络中的s-t增广路。对于增广路中的前向弧,置s(e)=u(e)-
f(e)。对于反向弧,置s(e)=f(e)
STEP3:计算crement=mins(e1),s(e2),…,s(ek)
STEP4:对于增广路中的前向弧,令f(e)=f(e)+crement;对于其中的反向弧,令f(e)=f(e)-crement,转STEP1。
关键点:寻找可增广路。决定了算法复杂度。
实现:Edmonds-Karp
通过采用了广度优先的搜索策略得以使其复杂度达到O(V*E^2)。
优化—>
Dinic算法(*)
Dinic算法的思想是为了减少增广次数,建立一个辅助网络L,L与原网络G具有相同的节点数,但边上的容量有所不同,在L上进行增广,将增广后的流值回写到原网络上,再建立当前网络的辅助网络,如此反复,达到最大流。分层的目的是降低寻找增广路的代价。
算法的时间复杂度为O(V^2*E)。其中m为弧的数目,是多项式算法。邻接表表示图,空间复杂度为O(V+E)。
2.预流推进算法
一般性的push-relabel算法:
时间复杂度达到O(V^2*E)。(*)
relabel-to-front算法->改进
最高标号预流推进:时间复杂度O(V^2*sqrt(E))
NO2.
最小费用最大流
求解最小费用流的步骤和求最大流的步骤几乎完全一致,只是在步骤1时选一条非饱和路时,应选代价和最小的路,即最短路。
步骤1.
选定一条总的单位费用最小的路,即要给定最小费用的初始可行流,而不是包含边数最小的路。
步骤2.
不断重复求最大流的步骤来进行,直到没有饱和路存在为止。然后计算每个路的总费用。
和Edmonds-Karp标号算法几乎一样,因为这两种算法都使用宽度优先搜索来来寻找增广路径,所以复杂度也相同,都是O(V*E^2)。
连续最短路算法
+
线性规划对偶性优化的原始对偶算法(*)
传说中,没见过,据说复杂度是O(V^3)
NO3.
有上下届的最大流和最小流(通过添加点来进行转化)(*)
NO4.
相关图论算法
二分图最大匹配:
加s和t构造最大流
专用算法:hungary算法
O(M*N)
二分图最佳匹配:
加s和t构造最小费用最大流
专用算法:KM算法
朴素的实现方法,时间复杂度为O(n^4)
加上松弛函数O(n^3)
最小路径覆盖:
顶点数-二分图的最大匹配
s-t最小边割集:
最大流最小割定理:最小割等于最大流
普通最小边割集:
Stoer-Wagner
Minimum
Cut
O(n^3)
二分图的最大独立集:
N
-
二分图的最大匹配(POJ
monthly)girls
and
boys
反证法证明
普通图的最大独立集是np问题。(*)

网络流

网络流

---by 蒟蒻鱼


解决什么问题:

有一自来水运送系统(可理解为有向图且有边权),起始点S,目标点为T,途中的每一个管道都有一个最大的容量(即权值)

  • 求S到T的最大水流量为多少

这样的问题就是网络流类问题,而此题就是要求最大流


如何求

Dinic算法 求最大流
(有些不理解,但请记住一些结论)

粗略的步骤:

1.找增广路,将路上的最小边权加入答案

2.进行一系列操作

重复上述操作直到找不出增广路就返回

详细的解释

2.的解释


  • 反悔机制

在每一条边加一条反向边维护这条边使其与其正边的值相加恒为原边权

什么意思?

简单地来讲就是原边权值加上某个值,反向边就减去这个值

  • 找增广路

在找到增广路时记得将路上的所有边权都减去增广路上的最小边的边权(tip 记得将每个边的反向边加上对应的值 即增广路上的最小边的边权)


好啦,开始打代码 个鬼

还没有说怎样存图呢

一种对于我十分新鲜的存图方法

链式向前星


利用链表存边,与邻接表存图只有这唯一一个区别但提速很大

具体声明与存储方式

int head[maxn]; //head[u]记录u点的头指针 
struct edge
{
    int to;  //指向的下一个点的编号 
    int cost; //边权 
    int next; //指向的同一起始点的下一条边的编号 方便广搜遍历 
} es[maxm*2]; //记录边以及它的反弧 
int cnt=0; //记录边的个数
void addedge(int u,int v,int cost) //添加边以及其反弧 
{
    es[cnt]=edge(v,cost,head[u]); //添加边 
    /*
    1.将新边的头指针指向原来U点的头指针  
    2.将原来U点的头指针指向新边
    3.并将新边的边权赋为cost 
    
    这三步操作即将此边放在链表的首位 
    */
    head[u]=cnt;  
    ++cnt;
    es[cnt]=edge(u,0,head[v]);  //添加反弧 
    /*
    进行与正边相反的操作
    并将反弧的初始权值赋为0 
    */
    head[u]=cnt;
    ++cnt;
}

好啦,说完上述分析以及存点方法

终于开始打模板题代码

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std; 
const int maxn=10007;
const int maxm=10007;
int l[maxn];  //记录当前点在增广路中的深度为深搜提供便利 
int head[maxn]; //head[u]记录u点的头指针 
struct edge
{
    int to;  //指向的下一个点的编号 
    int cost; //边权 
    int next; //指向的同一起始点的下一条边的编号 方便广搜遍历 
} es[maxm*2]; //记录边以及它的反弧 
int cnt=0; //记录边的个数即其编号 
void addedge(int u,int v,int cost) //添加边以及其反弧 
{
    es[cnt]=edge(v,cost,head[u]); //添加边 
    /*
    1.将新边的头指针指向原来U点的头指针  
    2.将原来U点的头指针指向新边
    3.并将新边的边权赋为cost 
    
    这三步操作即将此边放在链表的首位 
    */
    head[u]=cnt;  
    ++cnt;
    es[cnt]=edge(u,0,head[v]);  //添加反弧 
    /*
    进行与正边相反的操作
    并将反弧的初始权值赋为0 
    */
    head[u]=cnt;
    ++cnt;
}
bool ap(int s,int t)  //搜索是否能找到增广路 
{
    memset(l,0,sizeof(l));
    l[s]=1;
    queue<int> q; //利用队列存点更加便捷 
    q.push(s);
    while(!q.empty()) //广搜过程不需解释 
    {
        int u = q.front(); 
        q.pop();
        if(u==t) //如果搜索到了汇点 
            return  true;
        for(int v=head[u];v!=-1;v=es[v].next){
            int k=es.to;  //记录当前遍历到的下一个点 
            if(!l[k]&&es[v].cost){ //如果此点未被遍历且连接两个点的两条边权不为零 
                l[k]=l[u]+1;  
                q.push(v); 
            }
        } 
    }
    return false; //没有找到 
}
int dfs(int x,int t,int ans) //x为当前遍历到的点,t为汇点,ans为增广路的增益也是流量的限制 
{
    if(x==t)
        return ans;
    int sum=0;
    for(int i=head[x];i!=-1;i=es[i].next){
        if(es[i].cost&&l[x]==l[es[i].to]){ //如果改边流量没有用完且下一个点在增广路上的下一个点 
            int f=dfs(es.to,t,min(es[i].cost,ans-sum));
            es[i].cost-=f;
            es[i^1].cost+=f; //反弧加上相应值
            sum+=f;
            if(sum==ans) return ans; 
        }
    }
} 
int dinic(int s,int t) //s为源点,t为汇点 
{
    int ans=0;
    while(ap(s,t)) //重复找s到t的增广路
    {
        //for(int i=0;i<=t;++i){    
        //}
        ans+=dfs(s,t,0x3f3f3f3f);
    } 
    return ans;
} 

int main()
{
    
}

以上是关于帮我解释下网络流的主要内容,如果未能解决你的问题,请参考以下文章

C#流总结(文件流内存流网络流BufferedStreamStreamReader/StreamWriterTextReader/TextWriter)

费用流网络流24题P4014 分配问题.md

学习笔记:网络流

网络流

网络流

网络流