我学习Dinic算法主要是由于是做交互式图像分割时要计算Max-flow/Min-cut;
Dinic算法是计算Max-flow/Min-cut比较基础的一个算法;交互式图像分割是将图像每一个像素点看成图中的一个节点来构造图网络。所以一般网上介绍的用邻接矩阵的方式表示图网络就不适用了,所以我采用邻接表的方式表示图网络;
Dinic算法:
目的:计算加权图的最大流(有向图,无向图均可;这里我选择有向图,而且规定每一条边必须有一条反向边,但是每一条边及其反向边的容量不必相等,如果实际中i节点到j节点没有边相连,则在图中表示为i到j的边容量为0);
约定:
第0个节点作为源节点,表示为s;最后一个节点作为汇点表示为t图用G表示,边的集合用E表示,点的集合用V表示,节点u到v的边用(u,v)表示,边(u,v)的容量用c(u,v)表示,边(u,v)的流量用f(u,v)表示c(u,v) ≥0,f(u,v)≥0,c(u,v)为0则表示u到v不再连通
算法基本步骤:
-
根据图的邻接表,从源节点s出发,用广度优先搜索得到层次图;源节点的层次为表示为0,源节点的邻接节点的层次表示为1,其余节点以此类推
-
根据1)中得到的层次图,用深度优先搜索从s出发,计算从s到t的增广路径
-
如果2)中找到增广路径,则增广路径所经过的所有边的容量都减去增广路径中所有边中容量的最小值,得到残留容量,转到2);如果在深度优先搜索后步骤2)中找到过增广路径,但是经过若干次重复运算,找不到增广路径了,则转到1);如果在深度优先搜索以后一条增广路径都没找到过,则算法结束;
程序源代码:
#include "iostream"
#include "list"
#include "vector"
#include "stdlib.h"
#include "float.h"
using namespace std;
struct m_nNode;//申明节点的数据结构
struct m_nNeiborEdge //定义每个节点的邻接表,以及各边的容量,方向边
{
m_nNode * neiborNode;
float edgeCapacity;//边的容量
float * reEdgeCapacity;//指向反向边的容量的指针
};
//定义节点的数据结构
typedef struct m_nNode
{
int m_nValue;//节点的值
int m_nLevel;//节点在层次图中的层次
int m_nFlag;//节点访问与否的标志
vector<m_nNeiborEdge> listNeibor;//节点的邻接表
}* lp_Node;
//定义全局变量
lp_Node pNode;//记录图的数据结构
int num;//节点的个数
//获输入的图的数据
//输入数据时,每条边必须有反向边,各个节点的连通性由它们之间边的容量来衡量
//容量为0即表示该边不通
bool GetData ( )
{
if ( num > 0 )
{
pNode = new m_nNode [ num ];
cout << "内存分配完成" << endl;
}
int i,j;
int tempNum;//节点邻接节点的个数
int tempNode;
m_nNeiborEdge tempNeiborEdge;
for ( i = 0; i < num; i++ )
{
pNode [ i ].m_nValue = i;
cout << "请输入第 " << i << " 个节点的领结表的个数" << endl;
cin >> tempNum;
//输入各节点的邻接表及其容量
for ( j = 0; j < tempNum; j++ )
{
cout << "请输入节点" << i << "的第" << j << "个邻接节点" << endl;
cin >> tempNode;
tempNeiborEdge.neiborNode =&( pNode [ tempNode ]);
cout << "请输入节点" << i << "到节点" << tempNode << "的容量" << endl;
cin >> tempNeiborEdge.edgeCapacity;
pNode [ i ].listNeibor.push_back ( tempNeiborEdge );
}
}
//处理反向边
int m;
for ( i = 0; i < num; i++ )
{
for ( j = 0; j < pNode[i].listNeibor.size(); j++ )
{
tempNode = pNode [ i ].listNeibor.at(j).neiborNode->m_nValue;
for ( m = 0; m < pNode[tempNode].listNeibor.size(); m++ )
{
if ( pNode[i].m_nValue == pNode[ tempNode ].listNeibor.at (m ).neiborNode->m_nValue)
{
pNode [ i ].listNeibor.at ( j ).reEdgeCapacity = &( pNode [ tempNode ].listNeibor.at ( m ).edgeCapacity );
}
}
}
}
/输出上面个输入的数据,查看正确性//
for ( i = 0; i < num; i++ )
{
cout << "节点" << i << "的值为:" << pNode [ i ].m_nValue << endl;
for ( j = 0; j < pNode[i].listNeibor.size(); j++ )
{
cout << "节点" << i << "的第" << j << "个邻接点为" << pNode [ i ].listNeibor.at ( j ).neiborNode->m_nValue << endl;
cout << "节点" << i << "到节点" << pNode [ i ].listNeibor.at ( j ).neiborNode->m_nValue << "的容量为" << pNode [ i ].listNeibor.at ( j ).edgeCapacity << endl;
cout << "反向边的容量为" << *( pNode [ i ].listNeibor.at ( j ).reEdgeCapacity ) << endl;
}
}
return true;
}
//广度优先搜索,确定各节点的层次
bool BFS ( )
{
int i;
for ( i = 0; i < num; i++ )
{
pNode [ i ].m_nFlag = 0;//为0即表示没有被访问到
pNode [ i ].m_nLevel = -1;//层次为-1表示不可达
}
list<int> listNode;//遍历各节点邻接表时的临时队列
int nodeValue;//当前点的值
pNode [ 0 ].m_nLevel = 0;
nodeValue = pNode [ 0 ].m_nValue;
int terminalVal = pNode [ num - 1 ].m_nValue;//目的节点
listNode.push_back ( nodeValue );
pNode [ 0 ].m_nFlag = 1;//表示给节点已经在队列中了
while ( !listNode.empty() )
{
nodeValue = listNode.front();//当前访问的节点的值
listNode.pop_front ( );
pNode [ nodeValue ].m_nFlag = 2;//表示该节点已经弹出队列,该节点访问完成
if (nodeValue == terminalVal )
{
break;
}
for ( i = 0; i < pNode[nodeValue].listNeibor.size(); i++ )
{
if ( pNode[nodeValue].listNeibor.at(i).neiborNode->m_nFlag == 0 &&
pNode[nodeValue].listNeibor.at(i).edgeCapacity > FLT_MIN )
{
listNode.push_back ( pNode [ nodeValue ].listNeibor.at ( i ).neiborNode->m_nValue );
pNode [ nodeValue ].listNeibor.at ( i ).neiborNode->m_nLevel =
pNode [ nodeValue ].m_nLevel + 1 ;
pNode [ nodeValue ].listNeibor.at ( i ).neiborNode->m_nFlag = 1;
}
}
}
//输出各节点的层次,检验程序是否正确///
for ( i = 0; i < num; i++ )
{
cout << "节点" << pNode[i].m_nValue << "的层次为" << pNode [ i ].m_nLevel << endl;
cout << "节点" << pNode [ i ].m_nValue << "的Flag为" << pNode [ i ].m_nFlag << endl;
}
return true;
}
//深度优先搜索,寻找增广路径,并计算残留网络
//float型数据与0进行比较的问题
bool DFS ( )
{
int i;
bool Flag = false;
for ( i = 0; i < num; i++ )
{
pNode [ i ].m_nFlag = 0;//Flag为0表示节点没有被访问到
}
vector<int> listNode;
int nodeValue;
float minPathVal ;//增广路径中边的最小容量
int minEdgeIndex;//最小边中靠近汇点的节点
int pathSize;
int terminalVal = pNode[num-1].m_nValue;//汇点
nodeValue = pNode [ 0 ].m_nValue;
listNode.push_back ( nodeValue );
pNode [ 0 ].m_nFlag = 1;//Flag为1表示该节点已经在队列中
while ( !listNode.empty( ) )
{
nodeValue = listNode.back ( );
for ( i = 0; i < pNode[nodeValue].listNeibor.size(); i++ )
{
//当前节点的邻接节点的层次比当前节点大1,当前节点到邻接节点的容量大于0,邻接节点不在队列中
if ( pNode[nodeValue].listNeibor.at(i ).neiborNode->m_nLevel ==
pNode [ nodeValue ].m_nLevel + 1 && pNode [nodeValue ].listNeibor.at(i).edgeCapacity > FLT_MIN && pNode[nodeValue].listNeibor.at(i).neiborNode->m_nFlag == 0 )
{
nodeValue = pNode [ nodeValue ].listNeibor.at ( i ).neiborNode->m_nValue;
i = -1;
listNode.push_back ( nodeValue );
pNode [ nodeValue ].m_nFlag = 1;
//判断是否找到增广路径
int j,k,tempIndex;
if ( nodeValue == terminalVal )
{
Flag = true;//表示在层次途中可以找到增广路径
//寻找增广路径中的最小容量的边
minPathVal = FLT_MAX;
for ( j = 1; j < listNode.size(); j++ )
{
//寻找第j个节点在第j-1个节点的邻接表中的索引
for ( k = 0; k < pNode[listNode.at(j-1)].listNeibor.size(); k++ )
{
if ( pNode [ listNode.at ( j - 1 ) ].listNeibor.at(k).neiborNode->m_nValue ==listNode.at(j) )
{
tempIndex = k;
break;
}
}
if ( pNode[listNode.at(j -1)].listNeibor.at(tempIndex ).edgeCapacity < minPathVal)
{
minPathVal = pNode [ listNode.at ( j - 1 ) ].listNeibor.at ( tempIndex ).edgeCapacity;
minEdgeIndex = j;
}
}
for ( j = 0; j < listNode.size()-1; j++ )
{
//寻找listNode中第j+1个节点在第j个节点的邻接表中的索引值
for ( k = 0; k < pNode[listNode.at(j)].listNeibor.size(); k++ )
{
if ( pNode [ listNode.at ( j ) ].listNeibor.at(k).neiborNode->m_nValue ==
listNode.at(j+1))
{
tempIndex = k;
break;
}
}
//增广路径中所有边的容量减去该路径中容量的最小值,从而得到残留容量
pNode [ listNode.at ( j ) ].listNeibor.at ( tempIndex ).edgeCapacity -= minPathVal;
*( pNode [ listNode.at ( j ) ].listNeibor.at ( tempIndex ).reEdgeCapacity ) += minPathVal;
}
//找到增广路径,并处理完增广路径边的容量以后的回退
pathSize = listNode.size ( );
for ( k = 0; k < pathSize - minEdgeIndex; k++ )
{
pNode [ listNode.back ( ) ].m_nFlag = 0;
listNode.pop_back ( );
}
nodeValue = listNode.back ( );
}
}
}
//Flag设为2,表示在当前层次图中不可达
pNode [ listNode.back ( ) ].m_nFlag = 2;
listNode.pop_back ( );
}
return Flag;
}
//Dinic算法
void Dinic ( )
{
BFS ( );
while ( DFS ( ) )
{
BFS ( );
}
}
int main ( )
{
cout << "请输入节点的总个数" << endl;
cin >> num;
//bool Flag;
GetData ( );
Dinic ( );
getchar ( );
return 0;
}
最后将所有与源节点相连的边的初始容量减去用Dinic计算完成后的残留容量即得到最大流;