重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
ServletResponse中定义了如下两个方法
专业成都网站建设公司,做排名好的好网站,排在同行前面,为您带来客户和效益!成都创新互联公司为您提供成都网站建设,五站合一网站设计制作,服务好的网站设计公司,网站设计、成都网站建设负责任的成都网站制作公司!
getOutputStream();
getWriter();
对应获得字节流和字符流
由于HttpServletResponse继承自ServletResponse所以天生也具有以上两个获得输出流的方法
首先是网络流中的一些定义:
V表示整个图中的所有结点的集合.
E表示整个图中所有边的集合.
G = (V,E) ,表示整个图.
s表示网络的源点,t表示网络的汇点.
对于每条边(u,v),有一个容量c(u,v) (c(u,v)=0),如果c(u,v)=0,则表示(u,v)不存在在网络中。相反,如果原网络中不存在边(u,v),则令c(u,v)=0.
对于每条边(u,v),有一个流量f(u,v).
一个简单的例子.网络可以被想象成一些输水的管道.括号内右边的数字表示管道的容量c,左边的数字表示这条管道的当前流量f.
网络流的三个性质:
1、容量限制: f[u,v]=c[u,v]
2、反对称性:f[u,v] = - f[v,u]
3、流量平衡: 对于不是源点也不是汇点的任意结点,流入该结点的流量和等于流出该结点的流量和。
只要满足这三个性质,就是一个合法的网络流.
最大流问题,就是求在满足网络流性质的情况下,源点 s 到汇点 t 的最大流量。
求一个网络流的最大流有很多算法 这里首先介绍 增广路算法(EK)
学习算法之前首先看了解这个算法中涉及到的几个图中的定义:
**残量网络
为了更方便算法的实现,一般根据原网络定义一个残量网络。其中r(u,v)为残量网络的容量。
r(u,v) = c(u,v) – f(u,v)
通俗地讲:就是对于某一条边(也称弧),还能再有多少流量经过。
Gf 残量网络,Ef 表示残量网络的边集.
这是上面图的一个残量网络。残量网络(如果网络中一条边的容量为0,则认为这条边不在残量网络中。
r(s,v1)=0,所以就不画出来了。另外举个例子:r(v1,s) = c(v1,s) – f(v1,s) = 0 – (-f(s,v1)) = f(s,v1) = 4.
其中像(v1,s)这样的边称为后向弧,它表示从v1到s还可以增加4单位的流量。
但是从v1到s不是和原网络中的弧的方向相反吗?显然“从v1到s还可以增加4单位流量”这条信息毫无意义。那么,有必要建立这些后向弧吗?
显然,第1个图中的画出来的不是一个最大流。
但是,如果我们把s - v2 - v1 - t这条路径经过的弧的流量都增加2,就得到了该网络的最大流。
注意到这条路径经过了一条后向弧:(v2,v1)。
如果不设立后向弧,算法就不能发现这条路径。
**从本质上说,后向弧为算法纠正自己所犯的错误提供了可能性,它允许算法取消先前的错误的行为(让2单位的流从v1流到v2)
注意,后向弧只是概念上的,在程序中后向弧与前向弧并无区别.
**增广路
增广路定义:在残量网络中的一条从s通往t的路径,其中任意一条弧(u,v),都有r[u,v]0。
如图绿色的即为一条增广路。
看了这么多概念相信大家对增广路算法已经有大概的思路了吧。
**增广路算法
增广路算法:每次用BFS找一条最短的增广路径,然后沿着这条路径修改流量值(实际修改的是残量网络的边权)。当没有增广路时,算法停止,此时的流就是最大流。
**增广路算法的效率
设n = |V|, m = |E|
每次增广都是一次BFS,效率为O(m),而在最坏的情况下需要(n-2增广。(即除源点和汇点外其他点都没有连通,所有点都只和s与t连通)
所以,总共的时间复杂度为O(m*n),所以在稀疏图中效率还是比较高的。
hdoj 1532是一道可以作为模板题目练手。
模板代码:
[cpp] view plain copy print?
#include cstdio
#include cstring
#include iostream
#include string
#include algorithm
#include map
#include vector
using namespace std;
const int N = 1100;
const int INF = 0x3f3f3f3f;
struct Node
{
int to;//终点
int cap; //容量
int rev; //反向边
};
vectorNode v[N];
bool used[N];
void add_Node(int from,int to,int cap) //重边情况不影响
{
v[from].push_back((Node){to,cap,v[to].size()});
v[to].push_back((Node){from,0,v[from].size()-1});
}
int dfs(int s,int t,int f)
{
if(s==t)
return f;
used[s]=true;
for(int i=0;iv[s].size();i++)
{
Node tmp = v[s][i]; //注意
if(used[tmp.to]==false tmp.cap0)
{
int d=dfs(tmp.to,t,min(f,tmp.cap));
if(d0)
{
tmp.cap-=d;
v[tmp.to][tmp.rev].cap+=d;
return d;
}
}
}
return 0;
}
int max_flow(int s,int t)
{
int flow=0;
for(){
memset(used,false,sizeof(used));
int f=dfs(s,t,INF);
if(f==0)
return flow;
flow+=f;
}
}
int main()
{
int n,m;
while(~scanf("%d%d",n,m))
{
memset(v,0,sizeof(v));
for(int i=0;in;i++)
{
int x,y,z;
scanf("%d%d%d",x,y,z);
add_Node(x,y,z);
}
printf("%d\n",max_flow(1,m));
}
}
#includeiostream
#includequeue
using namespace std;
#define INF 1000000000
struct node{
int from;
int to;
int flow;
}f[1000];
int n,m;
int maxf[1000];
bool vis[1000];
void bfs()
{
queueint p;
int i;
vis[1]=true;
p.push(1);
while(!p.empty())
{
int q=p.front();
p.pop();
for(i=1;i=m;i++)
{
if(f[i].from==q)
{
if(vis[f[i].to]==0)
{
p.push(f[i].to);
vis[f[i].to]=true;
}
maxf[f[i].to]+=min(f[i].flow,maxf[q]);
}
}
}
coutmaxf[n]endl;
}
int main()
{
while(cinnm)
{
int i;
for(i=1;i=m;i++)
{
cinf[i].fromf[i].tof[i].flow;
}
memset(maxf,0,sizeof(maxf));
memset(vis,0,sizeof(vis));
maxf[1]=INF;
bfs();
}
return 0;
}
BFS解最大流。
1、augment path,直译为“增广路径”,其思想大致如下:
原有网络为G,设有一辅助图G',其定义为V(G') = V(G),E(G')初始值(也就是容量)与E(G)相同。每次操作时从Source点搜索出一条到Sink点的路径,然后将该路径上所有的容量减去该路径上容量的最小值,然后对路径上每一条边u,v添加或扩大反方向的容量,大小就是刚才减去的容量。一直到没有路为止。此时辅助图上的正向流就是最大流。
我们很容易觉得这个算法会陷入死循环,但事实上不是这样的。我们只需要注意到每次网络中由Source到Sink的流都增加了,若容量都是整数,则这个算法必然会结束。
寻找通路的时候可以用DFS,BFS最短路等算法。就这两者来说,BFS要比DFS快得多,但是编码量也会相应上一个数量级。
增广路方法可以解决最大流问题,然而它有一个不可避免的缺陷,就是在极端情况下每次只能将流扩大1(假设容量、流为整数),这样会造成性能上的很大问题,解决这个问题有一个复杂得多的算法,就是预推进算法。
2、push label,直译为“预推进”算法。
3、压入与重标记(Push-Relabel)算法
除了用各种方法在剩余网络中不断找增广路(augmenting)的Ford-Fulkerson系的算法外,还有一种求最大流的算法被称为压入与重标记(Push-Relabel)算法。它的基本操作有:压入,作用于一条边,将边的始点的预流尽可能多的压向终点;重标记,作用于一个点,将它的高度(也就是label)设为所有邻接点的高度的最小值加一。Push-Relabel系的算法普遍要比Ford-Fulkerson系的算法快,但是缺点是相对难以理解。
Relabel-to-Front使用一个链表保存溢出顶点,用Discharge操作不断使溢出顶点不再溢出。Discharge的操作过程是:若找不到可被压入的临边,则重标记,否则对临边压入,直至点不再溢出。算法的主过程是:首先将源点出发的所有边充满,然后将除源和汇外的所有顶点保存在一个链表里,从链表头开始进行Discharge,如果完成后顶点的高度有所增加,则将这个顶点置于链表的头部,对下一个顶点开始Discharge。
Relabel-to-Front算法的时间复杂度是O(V^3),还有一个叫Highest Label Preflow Push的算法复杂度据说是O(V^2*E^0.5)。我研究了一下HLPP,感觉它和Relabel-to-Front本质上没有区别,因为Relabel-to-Front每次前移的都是高度最高的顶点,所以也相当于每次选择最高的标号进行更新。还有一个感觉也会很好实现的算法是使用队列维护溢出顶点,每次对pop出来的顶点discharge,出现了新的溢出顶点时入队。
Push-Relabel类的算法有一个名为gap heuristic的优化,就是当存在一个整数0kV,没有任何顶点满足h[v]=k时,对所有h[v]k的顶点v做更新,若它小于V+1就置为V+1。
cpp程序: #includecstdio#includecstring#includealgorithm#includequeue#define inf 0x7fffffffusingnamespace std;int tt,kase;int nn,m;int H[45],X[1004],P[1004],flow[1004],tot,cap[1005];int d[45];int S,T;void add(int x,int y,int z){P[++tot]=y;X[tot]=H[x];H[x]=tot;flow[tot]=z;cap[tot]=flow[tot];}queueint q;bool bfs(){memset(d,0,sizeof(d));d[S]=1; int x;q.push(S);while(!q.empty()){x=q.front();q.pop();for(int i=H[x];i;i=X[i]){if(flow[i]0!d[P[i]]){d[P[i]]=d[x]+1;q.push(P[i]);}}}returnd[T];}int dfs(int x,int a){if(x==T||a==0)return a;int f=a,tmp;for(int i=H[x];i;i=X[i]){if(flow[i]0d[P[i]]==d[x]+1){tmp=dfs(P[i],min(flow[i],a));flow[i]-=tmp;a-=tmp;flow[i^1]+=tmp;if(!a)break;}}if(f==a)d[x]=-1;return f-a;}int Dinic(){int f=0;while(bfs())f+=dfs(S,inf);return f;}int main(){/**输入过程省略**/int maxflow=Dinic();printf(%d\n,maxflow);return 0;}