转载请说明文章出处:http://blog.sina.com.cn/s/blog_60a0e97e0101bfj9.html
因为最近在研究图像分割,看到经典方法graph cut涉及的最大流/最小割问题,查了很多资料,基本上很只讲了一些方法实现的思想,没有具体实现能地让我们直观地明白整个理论。后来啃了很多资料,结合自己编代码,终于把最大流/最小割的问题具体实现了(代码见文章最后)。下面开始详细地讲解最大流/最小割的具体实现步骤(理论部分随意在网上都可以找到很多)。
以下就是代码的实现,你需要做的只是在项目的文件夹中建立一个名为graph_data.txt 的文件,将下面提到的测试值放进去,该测试值与上面的图是对应的,除了第一二个数据分别表示图中的边数、顶点个数之外,下面每三个数据分别表示了节点1、节点2、节点1,2间的最大容量(capacity)。两种方法的结果肯定是一样的,都是以下结果,8是最大流值,后面的0、1、2表示顶点0、1、2属于最小割方案的集合S,其余点归入另一个集合/S,G={S, /S}就是最小割方案。
//Ford_Fulkerson.cpp : 定义控制台应用程序的入口点。
//测试值:
#include "stdafx.h"
#include
#include
#include
#include
#include
using namespace std;
#define INFI 1000
typedef struct _mark
{
int pre_suc;
int max_incr;
}MARK;
int iteration = 0;
const int N = 100;
list setS;
bool isMark[N], isCheck[N], isDone;
MARK markList[N];
int c[N][N], f[N][N];
int n; //顶点数
void Mark(int index, int pre_suc, int max_incr)
{
isMark[index] = true;
markList[index].pre_suc = pre_suc;
markList[index].max_incr = max_incr;
}
void Check(int i)
{
isCheck[i] = true;
for (int j=0; j
{
if (c[i][j]>0 && !isMark[j] && c[i][j]>f[i][j])
Mark(j, i, min(markList[i].max_incr, c[i][j]-f[i][j]));
if(c[j][i]>0 && !isMark[j] && f[j][i]>0)
Mark(j, -i, min(markList[i].max_incr, f[j][i]));
}
}
int Maxflow()
{
int flow =0;
for (int i=0; i
{
flow += f[0][i];
}
return flow;
}
void Mincut()
{
int i = 0;
while (i
{
if(isMark[i])
setS.push_back(i);
i++;
}
}
int IncrFlowAuxi(int index)//辅助函数:计算增广路径中的最大可增量
{
if(index==0) return markList[index].max_incr;
int prev = markList[index].pre_suc;
int maxIncr = markList[index].max_incr;
return min(maxIncr, IncrFlowAuxi(prev));
}
void IncrFlow()//增广路径的增加
{
iteration++;
int incr = IncrFlowAuxi(n-1); //最大可增量
int index = n-1;
int prev;
while(index!=0)
{
prev = markList[index].pre_suc;
f[prev][index] += incr; //增广路径增加后,相应的流量进行更新
index = prev;
}
}
//ford_fulkerson算法
int ford_fulkerson()
{
int i;
while (1)
{
isDone = true;
i=0;
while(i
{
if (isMark[i] && !isCheck[i]) //判断是否所有标记的点都已被检查:若是,结束整个算法
{
isDone = false;
break;
}
i++;
}
if (isDone) //算法结束,则计算最小割和最大流
{
Mincut();
return Maxflow();
}
while (i
{
if(isMark[i] && !isCheck[i]) {
Check(i);
i = 0;
}
if(isMark[n-1]) //如果汇t被标记,说明找到了一条增广路径,则增加该条路径的最大可增加量
{
IncrFlow();
memset(isMark+1, false, n-1); //增加该增广路径后,除了源s,其余标记抹去
memset(isCheck, false, n);
}
else i++;
}
}
}
int main()
{
int m, i, j;
fstream fptr;
fptr.open("graph_data2.txt",ios::in);
fptr>>m;
fptr>>n;
for (int k = 0; k < n; ++k)
{
memset(c[k], 0, sizeof(c[0][0])*n);
memset(f[k], 0, sizeof(f[0][0])*n); //初始各分支流量为0
memset(isMark, false, n);
memset(isCheck, false, n);
}
isMark[0] = true; //给源做永久标记
markList[0].max_incr = INFI;
markList[0].pre_suc = INFI;
while (!fptr.eof())
{
fptr>>i;
fptr>>j;
fptr>>c[i][j];
}
fptr.close();
printf("max flow = %d\n",ford_fulkerson());
printf("segment set S = {");
for(list::iterator i=setS.begin(); i!=setS.end(); i++)
{
printf("%d ",*i);
}
printf("}\n");
printf("iteration=%d",iteration);
printf("\n\n");
system("PAUSE");
}
//The Push-Relabel Algorithm 最大流的压入与重标记算法
#include "stdafx.h"
#include
#include
#include
#include
#include
using namespace std;
const int N = 100;
bool isMark[N] = {false};
bool isCheck[N] = {false};
bool flag[N]; //顶点是否在队列 vlist 中
int c[N][N], //有向边的容量
e[N], //余流
f[N][N], //流
h[N], //高度
n; //顶点数
list vlist, //待排除顶点
vNearArr[N]; //邻接表
void Push(int x, int y)
{
int df = min(e[x], c[x][y]- f[x][y]);
f[x][y] += df;
f[y][x] = -f[x][y];
e[x] -= df;
e[y] += df;
}
void Relabel(int x)
{
h[x] = n*2-1;
for (list::iterator iter = vNearArr[x].begin(); iter != vNearArr[x].end(); ++iter)
if (h[*iter] < h[x] && c[x][*iter] > f[x][*iter])
h[x] = h[*iter];
++h[x];
}
void Discharge(int x)
{
list::iterator iter = vNearArr[x].begin();
while (e[x] > 0) //有余流
{
if (iter == vNearArr[x].end())
{
Relabel(x);
iter = vNearArr[x].begin();
}
if (h[x] == h[*iter]+1 && c[x][*iter] > f[x][*iter])
{
Push(x, *iter);
if (e[*iter] > 0 && !flag[*iter])
vlist.push_back(*iter);
}
++iter;
}
}
void Check(int index)
{
isCheck[index] = true;
int i=0;
while (i
{
if(c[index][i]>0 && (c[index][i]-f[index][i]>0)) //有余流
isMark[i] = true;
i++;
}
}
void FindMinCut()//最小割存在于源s能够到达的所有点集合(包括s),即s能够通过余量到达该点
{
isMark[0] = true; //s永久可达到
int i=0;
while (i
{
if(isMark[i] && !isCheck[i]) {
Check(i);
i = 0;
}
else i++;
}
}
int Push_Relabel()
{
vlist.clear();
//初始化前置流
h[0] = n;
e[0] = 0;
memset(flag+1, 0, n-2); //1和n-1(即源和汇)不在 vlist 中
flag[0] = true;
flag[n-1] = true; //防止源、汇进入 vlist
for (int i = 1; i < n; ++i)
{
h[i] = 0; //初始化各顶点高度为 h(i)=0
f[0][i] = c[0][i]; //初始化源与其他与之相连的 边流量f(0,i)=边容量c(0,i)
f[i][0] = -c[0][i]; //反向边容量
e[0] -= c[0][i]; //初始化源的 点余量e(0)=-c(0,i)
e[i] = c[0][i]; //初始化其他 点余量e(i)=c(0,i)
if (c[0][i] > 0 && i != n-1)
{
vlist.push_back(i); //待排除顶点,压入栈
flag[i] = true;
}
}
//构造邻接表,每个点i的列表vNearArr[i]中存储与点i在图上相邻的点
for (int i = 0; i < n-1; ++i)
for (int j = i; ++j < n; )
if (c[j][i] > 0 || c[i][j] > 0)
{
vNearArr[i].push_back(j);
vNearArr[j].push_back(i);
};
//排除队列中的顶点
while (!vlist.empty())
{
int x = vlist.front();
Discharge(x);
vlist.pop_front();
flag[x] = false;
}
FindMinCut();
return e[n-1];
}
int main()
{
int m;
fstream fptr;
fptr.open("graph_data.txt",ios::in);
fptr>>m;
fptr>>n;
for (int i = 0; i < n; ++i)
{
memset(c, 0, sizeof(c[0][0])*n);
memset(f, 0, sizeof(f[0][0])*n);
vNearArr[i].clear();
}
int x, y, w;
while (!fptr.eof())
{
fptr>>x;
fptr>>y;
fptr>>c[x][y];
}
fptr.close();
printf("%d\n", Push_Relabel()); //计算从0(源)到 n-1(汇)的最大流
for (int i=0; i
{
if(isMark[i]) printf("%d ", i);
}
printf("\n");
system("PAUSE");
}