圆桌问题 网络流Isap算法

Posted wanluN1

tags:

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

圆桌问题 最大网络流Isap算法


图片来源《趣学算法》 人民邮电出版社 陈小玉

算法设计


图片来源《趣学算法》 人民邮电出版社 陈小玉

代码实现

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

const int INF=0x3fffffff;
const int N=100;
const int M=10000;
int top;
int h[N],pre[N],g[N];
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 m,int n);//输出结果 


int main(int argc,char**argv){
    init();
    int n=0,m=0,sum=0,total=0;//sum为总人数
    int cost=0;
    cout<<"输入代表团数量m与会议桌数量n:";
    cin>>m>>n;
    total=m+n; 
    cout<<"输入每个代表团的人数\\n";
    for(int i=1;i<=m;i++){
        cin>>cost;
        sum+=cost;
        add(0,i,cost);//源点到代表团的边,容量为代表团人数量
    }
    cout<<"输入每个会议桌可安排的人数\\n";
    for(int j=m+1;j<=total;j++){
        cin>>cost;
        add(j,total+1,cost);//会议桌到汇点的边的容量为会议桌可安排的数量
    }
    //架设代表团到会议桌的边
    for(int i=1;i<=m;i++){
        for(int j=m+1;j<=total;j++){
            add(i,j,1);//容量为1,因为每个桌子上只能有一个代表团的人
        }
    }
    printGraph(total+2);
    if(sum==Isap(0,total+1,total+2)){//不能有人落单没有分配到桌子
        cout<<"会议桌安排成功\\n";
        printResult(m,n);
    }else{
        cout<<"无法合理分配\\n";
    }
    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++;
}

//匹配算法
/*
@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;
}

void printResult(int m,int n){
	cout<<"方案:\\n";
	for(int i=1;i<=m;i++){
		//遍历其邻接边
		for(int j=V[i].first;~j;j=E[j].next){
			if(E[j].flow==1){
				cout<<i<<"--"<<E[j].v-m<<" \\n";
			}
		}
	} 
}


//输出邻接表
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");
    }
}


/*Test*/
/*
输入代表团数量m与会议桌数量n:4 5
输入每个代表团的人数
2 4 3 5
输入每个会议桌可安排的人数
3 4 2 5 4
网络邻接表:
[0]---flow:0 cap:5---[4]---flow:0 cap:3---[3]---flow:0 cap:4---[2]---flow:0 cap:2---[1]
[1]---flow:0 cap:1---[9]---flow:0 cap:1---[8]---flow:0 cap:1---[7]---flow:0 cap:1---[6]---flow:0 cap:1---[5]---flow:0 cap:0---[0]
[2]---flow:0 cap:1---[9]---flow:0 cap:1---[8]---flow:0 cap:1---[7]---flow:0 cap:1---[6]---flow:0 cap:1---[5]---flow:0 cap:0---[0]
[3]---flow:0 cap:1---[9]---flow:0 cap:1---[8]---flow:0 cap:1---[7]---flow:0 cap:1---[6]---flow:0 cap:1---[5]---flow:0 cap:0---[0]
[4]---flow:0 cap:1---[9]---flow:0 cap:1---[8]---flow:0 cap:1---[7]---flow:0 cap:1---[6]---flow:0 cap:1---[5]---flow:0 cap:0---[0]
[5]---flow:0 cap:0---[4]---flow:0 cap:0---[3]---flow:0 cap:0---[2]---flow:0 cap:0---[1]---flow:0 cap:3---[10]
[6]---flow:0 cap:0---[4]---flow:0 cap:0---[3]---flow:0 cap:0---[2]---flow:0 cap:0---[1]---flow:0 cap:4---[10]
[7]---flow:0 cap:0---[4]---flow:0 cap:0---[3]---flow:0 cap:0---[2]---flow:0 cap:0---[1]---flow:0 cap:2---[10]
[8]---flow:0 cap:0---[4]---flow:0 cap:0---[3]---flow:0 cap:0---[2]---flow:0 cap:0---[1]---flow:0 cap:5---[10]
[9]---flow:0 cap:0---[4]---flow:0 cap:0---[3]---flow:0 cap:0---[2]---flow:0 cap:0---[1]---flow:0 cap:4---[10]
[10]---flow:0 cap:0---[9]---flow:0 cap:0---[8]---flow:0 cap:0---[7]---flow:0 cap:0---[6]---flow:0 cap:0---[5]
[11]
初始化标高h={ 0-3 1-2 2-2 3-2 4-2 5-1 6-1 7-1 8-1 9-1 10-0 }
增广路径:10---9---4---0增流: 1
增广路径:10---8---4---0增流: 1
增广路径:10---7---4---0增流: 1
增广路径:10---6---4---0增流: 1
增广路径:10---5---4---0增流: 1
增广路径:10---9---3---0增流: 1
增广路径:10---8---3---0增流: 1
增广路径:10---7---3---0增流: 1
增广路径:10---9---2---0增流: 1
增广路径:10---8---2---0增流: 1
重贴标签后的高度
h[ ]= 2 2 2 2 1 1 3 1 1 0 -1
增广路径:10---6---2---0增流: 1
增广路径:10---5---2---0增流: 1
增广路径:10---9---1---0增流: 1
增广路径:10---8---1---0增流: 1
重贴标签后的高度
h[ ]= 2 2 2 2 1 1 3 1 1 0 -1
会议桌安排成功
方案:
1--5
1--4
2--5
2--4
2--2
2--1
3--5
3--4
3--3
4--5
4--4
4--3
4--2
4--1
*/















以上是关于圆桌问题 网络流Isap算法的主要内容,如果未能解决你的问题,请参考以下文章

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

ISAP网络流算法

最短增广路Isap算法 网络流

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

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

[洛谷 P3376] 网络最大流 | 模板 (ISAP 算法) 入门