配对方案Isap算法--线性规划网络流

Posted 11biscuits

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了配对方案Isap算法--线性规划网络流相关的知识,希望对你有一定的参考价值。

简略介绍

二分图:又称作二部图,设G=(V,E)是一个无向图,
如果结点集V可分割为两个互不相交的子集(V1,V2),
并且图中的每条边(i,j)所关联的两个结点i和j分别属于这两个不同的结点集(i<-V1,j<-V2)
匹配:在图论中,一个匹配是一个边的集合,其中任意两条边都没有公共结点。
最大匹配:一个图所有匹配中,边数最多的匹配,称为这个图的最大匹配。

V1 V2 边 1–6
1 5 1–7
2–5
2 6 3–7
4–5
3
7
4
配对网络,增加源点s->v1[i],与汇点v2[j]->t
*/

/*配对网络算法
(1) 构建网络:根据输入的数据,增加源点汇点,每条边的容量设为1、创建混合网络
(2) 求网络最大流
(3) 输出最大流值就是最大匹配对数
(4) 搜索V1结点的邻接表,流量为1的边对应的邻接点就是一个最佳配对方案

C++代码实现

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int INF=0x3fffff;
const int N=100;
const int M=10000;
int top;//V1 V2结点集总共的结点数量
int h[N],pre[N],graph[N];
int g[N];//存放 g[x] 高度为x的节点的总个数 


//邻接表相关数据结构
struct Vertex{
    int first;
}V[N];
struct Edge{
    int v,next;
    int cap,flow;
}E[M];


void init();//初始化
void add(int u,int v,int c);
void add_edge(int u,int v,int c);
void printGraph(int n);//输出邻接表
int Isap(int s,int t,int n);//匹配算法
void set_h(int t,int n);
void printResult(int n);//输出配对方案 

int main(void){
    int n,m,total;
    int u,v;
    cout<<"请输入女员工人数m和男员工人数n: \\n";
    cin>>m>>n;
    //初始化
    init();
    total=m+n;//V1 V2总共的结点数量

    //初始化网络
    for(int i=1;i<=m;i++){
        //源点到V1结点集中的点 0为源点
        add(0,i,1);
    }
    //将右边一层连接到汇点 
    for(int i=m+1;i<=total;i++){
        add(i,total+1,1);
    }

    cout<<"输入可以配和的V1与V2结点可组合的对,u--v,当u=-1且v=-1时结束输入\\n";
    while(cin>>u>>v,u+v!=-2){
        add(u,v,1);//u->v cap=1 v->u flow=0
    }

    //输出邻接表
    printGraph(total+2);


    cout<<"最大匹配对数: "<<Isap(0,total+1,total+2)<<endl;
    //输出邻接表
    printGraph(total+2);
    
    printResult(m);
    return 0;
}

//初始化
void init(){
    memset(V,-1,sizeof(V));//初始化每个结点的first为-1,初始化邻接表
    top=0;//边列表递增序号,存储边的列表使用多少了
}

//添加网络边
void add(int u,int v,int c){
    add_edge(u,v,c);//cap=c
    add_edge(v,u,0);//flow=0
}
void add_edge(int u,int v,int c){
    E[top].v=v;//邻接表插入新节点,头插法
    E[top].cap=c;
    E[top].flow=0;
    E[top].next=V[u].first;
    V[u].first=top++;
}

//输出邻接表
void printGraph(int n){
    cout<<"网络邻接表:\\n";
    for(int i=0;i<=n;i++){
        printf("[%d]",i);
        for(int j=V[i].first;~j;j=E[j].next){//-1 的反码为 0
            printf("---flow:%d cap:%d---[%d]",E[j].flow,E[j].cap,E[j].v);
        }
        printf("\\n");
    }
}

//匹配算法
/*
@Param s: 源点下标
@Param t: 汇点下标
@Param n: 总结点数 
*/
int Isap(int s,int t,int n){
    set_h(t,n);//进行标高
    int ans=0,u=s;//ans最大流量,u当前探索到的结点
    int d;
    while(h[s]<n){//源点高度小于总结点个数时 
        int i=V[u].first;
        if(u==s){//当前在源点时
            d=INF;
        }
        //搜索当前结点的邻接边
        for(;i!=-1;i=E[i].next){
            int v=E[i].v;//u-->v
            //判断是否满足探索条件:有可增量 且 h[u]-1=h[v]
            if(E[i].cap>E[i].flow&&h[u]-1==h[v]){//有可增量,且层级-1
                u=v;//满足条件则当前位置移到v           E[i]
                pre[v]=i;//设置v结点的前驱为i 即记录边u------->v
                //迭代最小增量
                d=min(d,E[i].cap-E[i].flow);
                if(u==t){//探索到了汇点
                    printf("增广路径:%d",t);
                    while(u!=s){
                        int j=pre[u];//即增广路汇点的前驱边E[j]
                        E[j].flow+=d;//E[j]流量增d
                        E[j^1].flow-=d;//j的反向边流量-d
                        /*
                        ^1:创建边时是成对创建的0^1=1 1^1=0 2^1=3 3^1=2 9^1=8 5^1=4
                        */
                        u=E[j^1].v;
                        cout<<"---"<<u;
                    }
                    printf("增流: %d\\n",d);//增广路径增流为路上的最小增量 
                    ans+=d;
                    d=INF;
                }
                break;//找到一条可行邻接边,即找到了下一个要去的点,退出for循环,停止寻找可行邻接边
            }
        }
        if(-1==i){//所有邻接边搜索完毕,无法前行
            if(--g[h[u]]==0){//该高度结点只有1个,算法结束
                break;
            }
            int hmin=n-1;
            for(int j=V[u].first;j!=-1;j=E[j].next){//搜索u的邻接边
                if(E[j].cap>E[j].flow){//有可增量
                    hmin=min(hmin,h[E[j].v]);
                }
            }
            h[u]=hmin+1;//可行邻接边的中的最小高度+1,否则将走不通的点的高度为节点个数 
            printf("重贴标签后的高度\\n");
            printf("h[ ]=");
            for(int i=1;i<=n;i++){
                printf(" %d",h[i]);
            }
            printf("\\n");
            ++g[h[u]];//重新贴标签后该高度的结点数+1
            if(u!=s){//当前结点不是源点
                u=E[pre[u]^1].v;//退回一步
            }
        }
    }
    return ans;
}


//广度优先标高函数 
void set_h(int t,int n){
    queue<int>Q;//广度优先搜索
    memset(h,-1,sizeof(h));//初始化各结点的高为-1
    memset(g,0,sizeof(g));//全部高度的结点数量为0
    h[t]=0;//汇点高度为0
    Q.push(t);//汇点入队列
    while(!Q.empty()){
        int v=Q.front();Q.pop();//对头出队列
        ++g[h[v]];//高度为h[v]的数量+1
        for(int i=V[v].first;i!=-1;i=E[i].next){//遍历结点v的临界点及v-->some
            int u=E[i].v;
            if(h[u]==-1){//还没有标记过
                h[u]=h[v]+1;
                Q.push(u);//入队列
            }
        }
    }
    cout<<"初始化标高";
    cout<<"h={";
    for(int i=0;i<n;i++){
        cout<<" "<<i<<"-"<<h[i];
    }
    cout<<" }"<<endl;
}

//输出配对结果 
/*
@Param n:女人个数,左层节点数 
*/
void printResult(int n){
	cout<<"配对方案:\\n";
	for(int i=1;i<=n;i++){
		//遍历其邻接边
		for(int j=V[i].first;~j;j=E[j].next){
			if(E[j].flow>0){
				cout<<i<<"--"<<E[j].v<<" ";
				break;
			}
		}
	} 
	cout<<"\\n";
}



测试样例

Test Simple
5 7
1 6
1 8
2 7
2 8
2 11
3 7
3 9
3 10
4 12
4 9
5 10
-1 -1

Console Output
请输入女员工人数m和男员工人数n:
5 7
输入可以配和的V1与V2结点可组合的对,u–v,当u=-1且v=-1时结束输入
1 6
1 8
2 7
2 8
2 11
3 7
3 9
3 10
4 12
4 9
5 10
-1 -1
网络邻接表:
[0]—flow:0 cap:1—[5]—flow:0 cap:1—[4]—flow:0 cap:1—[3]—flow:0 cap:1—[2]—flow:0 cap:1—[1]
[1]—flow:0 cap:1—[8]—flow:0 cap:1—[6]—flow:0 cap:0—[0]
[2]—flow:0 cap:1—[11]—flow:0 cap:1—[8]—flow:0 cap:1—[7]—flow:0 cap:0—[0]
[3]—flow:0 cap:1—[10]—flow:0 cap:1—[9]—flow:0 cap:1—[7]—flow:0 cap:0—[0]
[4]—flow:0 cap:1—[9]—flow:0 cap:1—[12]—flow:0 cap:0—[0]
[5]—flow:0 cap:1—[10]—flow:0 cap:0—[0]
[6]—flow:0 cap:0—[1]—flow:0 cap:1—[13]
[7]—flow:0 cap:0—[3]—flow:0 cap:0—[2]—flow:0 cap:1—[13]
[8]—flow:0 cap:0—[2]—flow:0 cap:0—[1]—flow:0 cap:1—[13]
[9]—flow:0 cap:0—[4]—flow:0 cap:0—[3]—flow:0 cap:1—[13]
[10]—flow:0 cap:0—[5]—flow:0 cap:0—[3]—flow:0 cap:1—[13]
[11]—flow:0 cap:0—[2]—flow:0 cap:1—[13]
[12]—flow:0 cap:0—[4]—flow:0 cap:1—[13]
[13]—flow:0 cap:0—[12]—flow:0 cap:0—[11]—flow:0 cap:0—[10]—flow:0 cap:0—[9]—flow:0 cap:0—[8]—flow:0 cap:0—[7]—flow:0 cap:0—[6]
[14]
初始化标高h={ 0-3 1-2 2-2 3-2 4-2 5-2 6-1 7-1 8-1 9-1 10-1 11-1 12-1 13-0 }
增广路径:13—10—5---0增流: 1
增广路径:13—9---4—0增流: 1
重贴标签后的高度
h[ ]= 2 2 2 2 2 1 1 1 1 3 1 1 0 -1
重贴标签后的高度
h[ ]= 2 2 2 2 2 1 1 1 3 3 1 1 0 -1
增广路径:13—7---3—0增流: 1
增广路径:13—11—2---0增流: 1
增广路径:13—8---1—0增流: 1
重贴标签后的高度
h[ ]= 2 2 2 2 2 1 1 1 3 3 1 1 0 -1
最大匹配对数: 5
网络邻接表:
[0]—flow:1 cap:1—[5]—flow:1 cap:1—[4]—flow:1 cap:1—[3]—flow:1 cap:1—[2]—flow:1 cap:1—[1]
[1]—flow:1 cap:1—[8]—flow:0 cap:1—[6]—flow:-1 cap:0—[0]
[2]—flow:1 cap:1—[11]—flow:0 cap:1—[8]—flow:0 cap:1—[7]—flow:-1 cap:0—[0]
[3]—flow:0 cap:1—[10]—flow:0 cap:1—[9]—flow:1 cap:1—[7]—flow:-1 cap:0—[0]
[4]—flow:1 cap:1—[9]—flow:0 cap:1—[12]—flow:-1 cap:0—[0]
[5]—flow:1 cap:1—[10]—flow:-1 cap:0—[0]
[6]—flow:0 cap:0—[1]—flow:0 cap:1—[13]
[7]—flow:-1 cap:0—[3]—flow:0 cap:0—[2]—flow:1 cap:1—[13]
[8]—flow:0 cap:0—[2]—flow:-1 cap:0—[1]—flow:1 cap:1—[13]
[9]—flow:-1 cap:0—[4]—flow:0 cap:0—[3]—flow:1 cap:1—[13]
[10]—flow:-1 cap:0—[5]—flow:0 cap:0—[3]—flow:1 cap:1—[13]
[11]—flow:-1 cap:0—[2]—flow:1 cap:1—[13]
[12]—flow:0 cap:0—[4]—flow:0 cap:1—[13]
[13]—flow:0 cap:0—[12]—flow:-1 cap:0—[11]—flow:-1 cap:0—[10]—flow:-1 cap:0—[9]—flow:-1 cap:0—[8]—flow:-1 cap:0—[7]—flow:0 cap:0—[6]
[14]
配对方案:
1–8 2–11 3–7 4–9 5–10

以上是关于配对方案Isap算法--线性规划网络流的主要内容,如果未能解决你的问题,请参考以下文章

试题库问题(最大流Isap) 网络流

ISAP网络流算法

最短增广路Isap算法 网络流

算法学习笔记(8.1): 网络最大流算法 EK, Dinic, ISAP

网络流算法总汇(ek,dinic,isap)

「网络流24题」1. 飞行员配对方案问题