甩开胆怯-,-...第四看算法导论最大流部分,有感了,太和谐了.

qq120848369 2010-06-01 04:28:40
第一次看是很久前,一堆证明,蒙了.
第二次看是很久之前之后,耐心全看了一遍,证明基本看不进去,了解最大流是在干什么.
第三次看前几天,想学最大二分匹配,了解到有匈牙利这个大家都说很简单的算法...于是直接看导论最大二分匹配部分,没发现什么联系- -.
第四看,中午看到litaoye说学会最大流就是算法入门水平了,非常迫不及待. ..下午躺在床上,最大流从头看起. 看到二分匹配之前...感悟颇多...证明全懂了,就是广搜对算法的优化的大片证明得最后那里稍微模糊一点...

先谈谈感触,高人们别吝啬啊,帮忙看看我应该怎么深入到实践(实现算法)中.

1.以前看就因为1,2个地方不懂,导致往后都不懂,即便坚持看下来也没有收获.
总结以前卡住的地方有:
1. 流图,残留图, 它们是干什么的,怎么那么多数...
2. 流图,残留图,它们怎么联系在一起的...
3. 把最大流,最小割原理没弄懂,结果学起来就感觉没底气,连道理都不讲怎么学.(很直接的就是增广路径的理解与流图残留网络的关系理解)

自己解决了上述问题:
1. 对于一个流图, 对每条边E(u,v),有一个容量c(u,v),还有目前流过它的流,f(u,v). 残留网络就是对流图中每条边求 c(u,v)-f(u,v),表示还可以流过多少,这个值也讲作为找增广路径时要用的图(即残留网络.). 以前卡住,很大原因是对于流守恒性质的f(u,v)=-f(v,u)的作用很难理解... 因为算法导论上并没有画出负的流,以至于迷茫了很久很久...
还有容量,算法导论中流图上画出来的有向边都是具有容量的边,比如c(u,v)>0,那么有一个u->v的有向边,而记c(v,u)是0.
但这并不影响残留网络的生成与增广路径的计算.具体的用第二个问题描述.
2.假设流图已经存在,并且已经有了一些流,那么对所有的点对(u,v)及(v,u),计算c(u,v)-f(u,v)(c(v,u)-f(v,u)),如果E(u,v)存在,那c(u,v)就是c(u,v),而且f(u,v)>0. 如果不存在,那c()=0,f(v,u)=-f(u,v)<0; 对所有的N*N个点对,都计算出这个值,就是残留网络的边了。
(我认为实现时不能这样,直接在流图中边找增广路径边计算这个值就可以了,这个值大于0则可以走,=0则不可以走)
然后,在残留网络中,找一个从s到t的路径p, 并取路径上残留流量最小的那个流量,给所有的路径上的边的流量加上这个残留最小流量(叫残流吧。。)。即,假设(u,v)是从s到t增广路径上的一条边,那么就给f(u,v)+残流,给f(v,u)-残流。(这里当时也是卡住了,现在理解意义所在了)。 至此结束,然后在计算残流网络,再找增广路径。。一直到找不到增广路径为止。。。

3.假设流图中已经有了一些流量,那么对图任意割, 路径上的流量和都相等(反向边的流量是负值), 容量不一定相等。
那一定存在一个最小容量割来限制流量总和不能超越这个限制。。。


以上就是理解,大家帮忙看看有错没。 另外,我想先自己实现一下这个算法,用广搜的方法。
实现起来,我就用记录每个结点的前驱与前驱之前的最小残留,找增广路径的同时计算残留网,找到路径后,根据前驱回溯上去,对每个顺序走来的边(u,v)的流加上最小残留,对(v,u)的流减去最小残留。

这样实现可以么。。
...全文
485 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
qq120848369 2010-06-03
  • 打赏
  • 举报
回复
结贴,结贴率直线下降.
diablox0147 2010-06-03
  • 打赏
  • 举报
回复
不错,lz在进步。。。。而我却在原地踏步
超级大笨狼 2010-06-02
  • 打赏
  • 举报
回复
我觉得有些东西,一本书可能会导致你看不懂,要多换几本书看不同的说法,才能明白。
INGDI 2010-06-02
  • 打赏
  • 举报
回复
顶,佩服楼主
qq120848369 2010-06-02
  • 打赏
  • 举报
回复
#include <iostream>   
#include <queue>
using namespace std;

//Relabel()函数内用
#define MAX 100000

//包括源点(0),汇点(5),所有点在内,一共6个点,算法导论405页的图.
const int numV=6;

//记录是否在队列内(具有余流的点(溢出点))
bool inQueue[numV];

//顶点的高度h[]
int h[numV];

//顶点的余流e[],e[]>0表示溢出
int e[numV];

//流网络
struct
{
int c; //容量
int f; //流
}flowNet[numV][numV];

//initializePreFlow(queue<int> &)
void initializePreFlow(queue<int> &eQ)
{
//初始化高度与余流,清理队列标记
for(int i=0;i<numV;++i)
{
h[i]=0;
e[i]=0;
inQueue[i]=false;
}
//处理源s的高度
h[0]=numV;
//初始化前置流
for(int i=1;i<numV;++i)
{
flowNet[0][i].f+=flowNet[0][i].c;
flowNet[i][0].f-=flowNet[0][i].c;
e[0]-=flowNet[0][i].c;
e[i]+=flowNet[0][i].c;
if(e[i]>0&&i!=5) //进入队列的必须是e[]>0的,而且不能是汇点***********
{
eQ.push(i);
inQueue[i]=true;
}
}
}

//relabel() 重标记
void relabel(int u)
{
int minH=MAX;

for(int i=0;i<numV;++i)
{
if((flowNet[u][i].c-flowNet[u][i].f)>0&&h[i]<minH)
{
minH=h[i];
}
}

h[u]=1+minH;
}


//push()
void push(int u,int v)
{
int dFlow=e[u]<(flowNet[u][v].c-flowNet[u][v].f) ? e[u]:(flowNet[u][v].c-flowNet[u][v].f);
flowNet[u][v].f+=dFlow;
flowNet[v][u].f-=dFlow;
e[u]-=dFlow;
e[v]+=dFlow;
}

//genericPushRelabel() 实现压入重标记算法
int genericPushRelabel()
{
queue<int> eQ;
initializePreFlow(eQ);
int u,v;
int sum=0;

while(!eQ.empty())
{
u=eQ.front();
//寻找可以压入的边(u,v)
for(v=0;v<numV;++v)
{
if((flowNet[u][v].c-flowNet[u][v].f)>0&&(h[u]==h[v]+1))
{
break;
}
}
//如果找到可以压入的边
if(v!=numV)
{
push(u,v); //压入流

if(0==e[u])//如果u没有余流,则成为死结点
{
inQueue[u]=false;
eQ.pop();
}

if(inQueue[v]==false&&e[v]>0) //如果v不在队列内,且余流>0 **********
{
if(v!=5&&v!=0) //非源非终,因为源与终都不是溢出点
{
inQueue[v]=true;
eQ.push(v);
}
}
}
//没有可以压入的边,根据引理26.15,u肯定可以重标记
else
{
relabel(u);
}
}
return e[5]; //返回汇点t的余流就可以了
}

int main()
{
for(int i=0;i<numV;++i)
{
for(int j=0;j<numV;++j)
{
cin>>flowNet[i][j].c; //有边为实际容量,无边为0,flowNet(u,u)=0
flowNet[i][j].f=0;
}
}

cout<<genericPushRelabel()<<endl;
return 0;
}


改了一些小细节,为了保证在队列内的都是余流e[]>0的,而且非源点非汇点.
这样应该能保证和算法吻合了吧,求解答.
fansOfBnb 2010-06-02
  • 打赏
  • 举报
回复
Up........
qq120848369 2010-06-02
  • 打赏
  • 举报
回复
这个预流压入,写完感觉和导论上讲的故事有点相似貌似,算法太神奇了.只能了解大概,不能说明原理.
这样也行么..
qq120848369 2010-06-02
  • 打赏
  • 举报
回复
[code=C/C++]#include <iostream>   
#include <queue>
using namespace std;

//Relabel()函数内用
#define MAX 100000

//包括源点(0),汇点(5),所有点在内,一共6个点,算法导论405页的图.
const int numV=6;

//记录是否在队列内(具有余流的点(溢出点))
bool inQueue[numV];

//顶点的高度h[]
int h[numV];

//顶点的余流e[],e[]>0表示溢出
int e[numV];

//流网络
struct
{
int c; //容量
int f; //流
}flowNet[numV][numV];

//initializePreFlow(queue<int> &)
void initializePreFlow(queue<int> &eQ)
{
//初始化高度与余流,清理队列标记
for(int i=0;i<numV;++i)
{
h[i]=0;
e[i]=0;
inQueue[i]=false;
}
//处理源s的高度
h[0]=numV;
//初始化前置流
for(int i=1;i<numV;++i)
{
flowNet[0][i].f+=flowNet[0][i].c;
flowNet[i][0].f-=flowNet[0][i].c;
e[0]-=flowNet[0][i].c;
e[i]+=flowNet[0][i].c;
if(e[i]>0)
{
eQ.push(i);
inQueue[i]=true;
}
}
}

//relabel() 重标记
void relabel(int u)
{
int minH=MAX;

for(int i=0;i<numV;++i)
{
if((flowNet[u][i].c-flowNet[u][i].f)>0&&h[i]<minH)
{
minH=h[i];
}
}

h[u]=1+minH;
}


//push()
void push(int u,int v)
{
int dFlow=e[u]<(flowNet[u][v].c-flowNet[u][v].f) ? e[u]:(flowNet[u][v].c-flowNet[u][v].f);
flowNet[u][v].f+=dFlow;
flowNet[v][u].f-=dFlow;
e[u]-=dFlow;
e[v]+=dFlow;
}

//genericPushRelabel() 实现压入重标记算法
int genericPushRelabel()
{
queue<int> eQ;
initializePreFlow(eQ);
int u,v;
int sum=0;

while(!eQ.empty())
{
u=eQ.front();
//寻找可以压入的边(u,v)
for(v=0;v<numV;++v)
{
if((flowNet[u][v].c-flowNet[u][v].f)>0&&(h[u]==h[v]+1))
{
break;
}
}
//如果找到可以压入的边
if(v!=numV)
{
push(u,v); //压入流

if(0==e[u])//如果u没有余流,则成为死结点
{
inQueue[u]=false;
eQ.pop();
}

if(inQueue[v]==false&&e[v]!=0) //如果v不在队列内,且余流!=0
{
if(v!=5&&v!=0) //非源非终,因为源与终都不是溢出点
{
inQueue[v]=true;
eQ.push(v);
}
}
}
//没有可以压入的边,根据引理26.15,u肯定可以重标记
else
{
relabel(u);
}
}
return e[5]; //返回汇点t的余流就可以了
}

int main()
{
for(int i=0;i<numV;++i)
{
for(int j=0;j<numV;++j)
{
cin>>flowNet[i][j].c; //有边为实际容量,无边为0,flowNet(u,u)=0
flowNet[i][j].f=0;
}
}

cout<<genericPushRelabel()<<endl;
return 0;
}


预流压入写的最大流. 准备学最小费用最大流.

[/code]
qq120848369 2010-06-02
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 superdullwolf 的回复:]
我觉得有些东西,一本书可能会导致你看不懂,要多换几本书看不同的说法,才能明白。
[/Quote]

有道理,我网上翻来翻去,百度一会google一会,再看看导论,找个代码,乱七八糟的慢慢就懂一点了..

qq120848369 2010-06-01
  • 打赏
  • 举报
回复
是不是应该先学匈牙利,再学KM,再学HopcroftKarp。
什么学习顺序比较好。
qq120848369 2010-06-01
  • 打赏
  • 举报
回复
看了俩小时压入和重标记那块,例子生动,可惜描述不清- -.
是不是应该看HopcroftKarp和KM了。
  • 打赏
  • 举报
回复
嗯,挺好的!我现在就落后了,哎
qq120848369 2010-06-01
  • 打赏
  • 举报
回复
共勉。把网络流继续看完。
augustinlouis 2010-06-01
  • 打赏
  • 举报
回复
赞lz一个,记得网络流当时我学了好久才有点感觉。。。直到现在,在建模方面还有待提高。。。一起加油!!
qq120848369 2010-06-01
  • 打赏
  • 举报
回复
夸得不好意思了- -. 我尽量多学点知识,对自己提高是潜移默化的.
绿色夹克衫 2010-06-01
  • 打赏
  • 举报
回复
从我开始知道有二分图匹配这么回事儿,到从导论上看到二分图匹配,相隔了有1年多的时间。

网上的资料虽然不少,但多半是程序配一些看不太懂的说明,如果不是像导论这样,有图解,估计我就算到现在,可能还看不懂呢。

不过导论上的二分图和最大流,很难让我跟以前看到的那些题目联系在一起,也就是说知道了二分图匹配的算法,还是无法自己建模,把问题转化为已知模型。反而是后来了解了KM算法,才搞定这些问题。

不过得说LZ的天赋真的很不错,记得刚来的时候,也就是刚刚学习了DP,这么快都学到二分图了,确实很厉害!
michael122 2010-06-01
  • 打赏
  • 举报
回复
赞lz踏实的学习态度,加油~
qq120848369 2010-06-01
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 superdullwolf 的回复:]
我的导论自从买来就放在架子上给同事翻阅,后来失踪了,最近又有一小弟说在他那里,他买了本新的明天寄到我公司来。
[/Quote]

哪里啊,导论刚买的时候热情很高, 结果看了两页就看不懂了, 都尘封起来了 -,-
今天litaoye说这是入门...我又伤心又着急- -... 经过1个多月的酝酿,时机成熟,一蹴而就了..
以后一起看,碰到问题一起学习一下.

贴上我成功的代码,另外打听一下,紧接着应该学哪个,重标记与压入么? 用最大流实现一下二分图匹配.

#include <iostream>
#include <queue>
using namespace std;

//BFS()用
#define MAX 100000

//包括源点(0),汇点(5),所有点在内,一共6个点,算法导论405页的图.
const int numV=6;

//广搜记录是否已访问
bool visited[numV];

//流网络
struct
{
int c; //容量
int f; //流
}flowNet[numV][numV];

//广搜Info
struct
{
int v; //前驱结点号
int minF; //路径上最小残留
}pre[numV];

//BFS()
bool BFS()
{
queue<int> Q;
Q.push(0);
visited[0]=true;
int unDead;

while(!Q.empty())
{
unDead=Q.front();
Q.pop();
for(int i=0;i<numV;++i)
{
if(visited[i]==false&&i!=unDead&&(flowNet[unDead][i].c-flowNet[unDead][i].f)>0)
{
visited[i]=true;
Q.push(i);
pre[i].v=unDead;
pre[i].minF=( pre[unDead].minF > (flowNet[unDead][i].c-flowNet[unDead][i].f) ) ?
(flowNet[unDead][i].c-flowNet[unDead][i].f):pre[unDead].minF;
if(numV-1==i)
{
return true;
}
}
}
}

return false;
}

//Ford-Fulkerson
int Ford_Fulkerson()
{
pre[0].v=-1;
pre[0].minF=MAX;
memset(visited,0,sizeof(visited));

while(BFS()==true)
{
int nowV=numV-1;
int preV=pre[nowV].v;
int minF=pre[nowV].minF;
while(preV!=-1)
{
flowNet[preV][nowV].f+=minF;
flowNet[nowV][preV].f-=minF;
nowV=preV;
preV=pre[nowV].v;
}
memset(visited,0,sizeof(visited));
}

int totalFlow=0;
for(int i=1;i<numV;++i)
{
totalFlow+=flowNet[0][i].f>0? flowNet[0][i].f:0;
}
return totalFlow;
}

int main()
{
for(int i=0;i<numV;++i)
{
for(int j=0;j<numV;++j)
{
cin>>flowNet[i][j].c; //有边为实际容量,无边为0,flowNet(u,u)=0
flowNet[i][j].f=0;
}
}
cout<<Ford_Fulkerson()<<endl;
return 0;
}

/*
示意图:

(s)0 1 2 3 4 (t)5
(s)0 0 16 13 0 0 0
1 0 0 10 12 0 0
2 0 4 0 0 14 0
3 0 0 9 0 0 20
4 0 0 0 7 0 4
(t)5 0 0 0 0 0 0

复制粘贴的数据:


0 16 13 0 0 0
0 0 10 12 0 0
0 4 0 0 14 0
0 0 9 0 0 20
0 0 0 7 0 4
0 0 0 0 0 0

*/
超级大笨狼 2010-06-01
  • 打赏
  • 举报
回复
我的导论自从买来就放在架子上给同事翻阅,后来失踪了,最近又有一小弟说在他那里,他买了本新的明天寄到我公司来。
超级大笨狼 2010-06-01
  • 打赏
  • 举报
回复
恭喜,我也找时间看一下。

导论被你翻烂了吧?

导论我还没看呢,嘿嘿,偶就是算法门外水平,在这里答水题,以我昏昏使人昭昭。
加载更多回复(1)

33,009

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧