问题提出:
N个智能体,现在有个任务,就是让N个智能体要到N个目标位置,目标位置不能重复,目标位置与机器人一一对应,要使得期间所有所走的距离之和最短,求解最优任务分配。
问题抽象:
有N个师父, N个徒弟,每个徒弟只能选者一个师父学习, 每个师父只能带一个徒弟; 每个师父有自己的技能,每个徒弟有自己学习的天分, 当徒弟 j 在师父 i 门下学习, 产生效益为 Aij, 问如何安排使得总效益最大?
求解过程:
定义数据格式:
m
i
:
第
i
个
师
父
,
i
=
1
,
2
,
3
,
⋯
,
n
m_i: 第 i 个师父, i=1,2,3,⋯,n
mi:第i个师父,i=1,2,3,⋯,n
t
j
:
第
j
个
徒
弟
,
j
=
1
,
2
,
3
,
⋯
n
,
t_j: 第 j 个徒弟,j=1,2,3,⋯n,
tj:第j个徒弟,j=1,2,3,⋯n,
x
i
j
:
第
j
个
徒
弟
是
否
在
第
i
个
师
父
门
下
学
习
:
1
,
是
;
2
不
是
.
x_{ij}: 第j 个徒弟是否在第i 个师父门下学习: 1, 是; 2 不是.
xij:第j个徒弟是否在第i个师父门下学习:1,是;2不是.
a
i
j
:
第
j
个
徒
弟
在
第
i
个
师
父
门
下
学
习
产
生
的
效
益
.
a_{ij}:第j 个徒弟在第i 个师父门下学习产生的效益.
aij:第j个徒弟在第i个师父门下学习产生的效益.
数学模型:
y
=
m
a
x
∑
i
=
1
n
a
i
j
.
x
i
j
(
式
0
)
y=max \sum_{i=1}^n a_{ij}.x_{ij} { ( 式0)}
y=maxi=1∑naij.xij(式0)
∑
i
=
1
n
x
i
j
=
1
,
∀
j
=
1
,
2
,
3
,
⋯
,
n
(
式
1
)
\sum_{i=1}^n x_{ij} =1 , \forall j=1,2,3,⋯,n { ( 式1)}
i=1∑nxij=1,∀j=1,2,3,⋯,n(式1)
∑
j
=
1
n
x
i
j
=
1
,
∀
i
=
1
,
2
,
3
,
⋯
,
n
(
式
2
)
\sum_{j=1}^n x_{ij} =1 , \forall i=1,2,3,⋯,n { ( 式2)}
j=1∑nxij=1,∀i=1,2,3,⋯,n(式2)
x i j ∈ { 0 , 1 } ∀ i = 1 , 2 , 3 , ⋯ , n ; j = 1 , 2 , 3 , ⋯ , n ( 式 3 ) x_{ij} \in {\lbrace0,1}\rbrace \forall i=1,2,3,⋯,n;j=1,2,3,⋯,n (式3) xij∈{0,1}∀i=1,2,3,⋯,n;j=1,2,3,⋯,n(式3)
- 式0 为目标函数, 即最大化总效益;
- 式1 表示一个师父只能带一个徒弟;
- 式2 表示一个徒弟只能跟一个师父学习;
- 式3表示师父-徒弟分配是原子操作,要么分配要么不分配。
在这个抽象的例子中,我们把
师父
⟺
\iff
⟺智能体(机器人)
徒弟
⟺
\iff
⟺要去的目标位置位置
效益
⟺
\iff
⟺智能体(机器人)当前位置与目标位置二者之间距离
当然这个例子中是求最小,师父徒弟的例子是求最大,道理是一样的。
匈牙利算法
解决分配问题最常用的是匈牙利算法。
按照知乎上某位博主的支付报酬、矩阵求解思路:
若从指派问题的系数矩阵的某行(列)各元素中分别减去或者加上常数k,其最优任务分解问题不变。
这里是引用
通过寻找“0”元素巧妙得出0报酬的指派思路:
给出的代码如下:
#include <stdio.h>
typedef struct matrix
{
int cost[101][101];
int zeroelem[101][101];
int costforout[101][101];
int matrixsize;
}MATRIX;
MATRIX hungary;
int result[5041][2]; //用来储存解的结果,第一列表示工人第二列表示工件
void zeroout(MATRIX &hungary); //减去行列的最小值得到零元素
void circlezero(MATRIX &hungary); //圈出单行列零元素
void twozero(MATRIX &hungary); //圈出行列存在两个以上的零元素
void judge(MATRIX &hungary,int result[2000][2]); //判断是否符合匈牙利算法条件
void refresh(MATRIX &hungary); //不符合条件,对矩阵进行变形
void output(int result[2000][2],MATRIX hungary); //结果输出
MATRIX input(); //初始输入
int main()
{
result[0][0]=0;
hungary=input();
zeroout(hungary);
circlezero(hungary);
output(result,hungary);
}
MATRIX input()
{
int i,j;
matrix hungary;
printf("指派问题的匈牙利解法\n");
printf("请输入cost矩阵的阶数:\n");
scanf("%d",&hungary.matrixsize);
printf("请输入代表工人和工件的%d阶矩阵:\n",hungary.matrixsize);
for(i=1;i<=hungary.matrixsize;i++)
for(j=1;j<=hungary.matrixsize;j++)
{
scanf("%d",&hungary.cost[i][j]);
hungary.costforout[i][j]=hungary.cost[i][j];
}
return hungary;
}
void zeroout(MATRIX &hungary)
{
int i,j;
int tem; //表示同行的最大元素或同列的最大元素
for(i=1;i<=hungary.matrixsize;i++) //减去同行最大元素
{
tem=hungary.cost[i][1];
for(j=2;j<=hungary.matrixsize;j++)
if(hungary.cost[i][j]<tem)
tem=hungary.cost[i][j];
for(j=1;j<=hungary.matrixsize;j++)
hungary.cost[i][j]=hungary.cost[i][j]-tem;
}
for(j=1;j<=hungary.matrixsize;j++) //减去同列最大元素
{
tem=hungary.cost[1][j];
for(i=2;i<=hungary.matrixsize;i++)
if(hungary.cost[i][j]<tem)
tem=hungary.cost[i][j];
for(i=1;i<=hungary.matrixsize;i++)
hungary.cost[i][j]=hungary.cost[i][j]-tem;
}
}
void circlezero(MATRIX &hungary)
{
int i,j,p;
int flag;
for(i=0;i<=hungary.matrixsize;i++) //在矩阵外面构建半圈矩阵标记0的个数;
hungary.cost[i][0]=0;
for(j=1;j<=hungary.matrixsize;j++)
hungary.cost[0][j]=0;
for(i=1;i<=hungary.matrixsize;i++)
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.cost[i][j]==0)
{
hungary.cost[i][0]++;
hungary.cost[0][j]++;
hungary.cost[0][0]++;
}
for(i=0;i<=hungary.matrixsize;i++) //新建一个矩阵
for(j=0;j<=hungary.matrixsize;j++)
hungary.zeroelem[i][j]=0;
flag=hungary.cost[0][0]+1; //flag = 0的总个数+1
while(hungary.cost[0][0]<flag)
{
flag=hungary.cost[0][0]; //行列单0的情况,
for(i=1;i<=hungary.matrixsize;i++) //第一遍先行后列
{
if(hungary.cost[i][0]==1)
{
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.cost[i][j]==0&&hungary.zeroelem[i][j]==0)
break;
hungary.zeroelem[i][j]=1;
hungary.cost[i][0]--;
hungary.cost[0][j]--;
hungary.cost[0][0]--;
if(hungary.cost[0][j]>0)
for(p=1;p<=hungary.matrixsize;p++)
if(hungary.cost[p][j]==0&&hungary.zeroelem[p][j]==0)
{
hungary.zeroelem[p][j]=2;
hungary.cost[p][0]--;
hungary.cost[0][j]--;
hungary.cost[0][0]--;
}
}
}
for(j=1;j<=hungary.matrixsize;j++) // 第二遍先列后行
{
if(hungary.cost[0][j]==1)
{
for(i=1;i<=hungary.matrixsize;i++)
if(hungary.cost[i][j]==0&&hungary.zeroelem[i][j]==0)
break;
hungary.zeroelem[i][j]=1;
hungary.cost[i][0]--;
hungary.cost[0][j]--;
hungary.cost[0][0]--;
if(hungary.cost[i][0]>0)
for(p=1;p<=hungary.matrixsize;p++)
if(hungary.cost[i][p]==0&&hungary.zeroelem[i][p]==0)
{
hungary.zeroelem[i][p]=2;
hungary.cost[i][0]--;
hungary.cost[0][p]--;
hungary.cost[0][0]--;
}
}
}
}
if(hungary.cost[0][0]>0)
twozero(hungary);
else
judge(hungary,result);
}
void judge(MATRIX &hungary,int result[5041][2])
{
int i,j;
int num=0; //线的条数
int start; //每组解的储存开始位置
for(i=1;i<=hungary.matrixsize;i++)
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.zeroelem[i][j]==1)
num++; //划线的条数
if(num==hungary.matrixsize)
{
start=result[0][0]*hungary.matrixsize+1;
for(i=1;i<=hungary.matrixsize;i++)
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.zeroelem[i][j]==1)
{
result[start][0]=i;
result[start++][1]=j;
}
result[0][0]++;
}
else
refresh(hungary);
}
void twozero(MATRIX &hungary)
{
int i,j;
int p,q;
int m,n;
int flag;
MATRIX backup;
for(i=1;i<=hungary.matrixsize;i++)
if(hungary.cost[i][0]>0)
break;
if(i<=hungary.matrixsize)
{
for(j=1;j<=hungary.matrixsize;j++)
{
backup=hungary;//备份以寻找多解
if(hungary.cost[i][j]==0&&hungary.zeroelem[i][j]==0)
{
hungary.zeroelem[i][j]=1;
hungary.cost[i][0]--;
hungary.cost[0][j]--;
hungary.cost[0][0]--;
for(q=1;q<=hungary.matrixsize;q++)
if(hungary.cost[i][q]==0&&hungary.zeroelem[i][q]==0)
{
hungary.zeroelem[i][q]=2;
hungary.cost[i][0]--;
hungary.cost[0][q]--;
hungary.cost[0][0]--;
}
for(p=1;p<=hungary.matrixsize;p++)
if(hungary.cost[p][j]==0&&hungary.zeroelem[p][j]==0)
{
hungary.zeroelem[p][j]=2;
hungary.cost[p][0]--;
hungary.cost[0][j]--;
hungary.cost[0][0]--;
}
flag=hungary.cost[0][0]+1;
while(hungary.cost[0][0]<flag)
{
flag=hungary.cost[0][0];
for(p=i+1;p<=hungary.matrixsize;p++)
{
if(hungary.cost[p][0]==1)
{
for(q=1;q<=hungary.matrixsize;q++)
if(hungary.cost[p][q]==0&&hungary.zeroelem[p][q]==0)
break;
hungary.zeroelem[p][q]=1;
hungary.cost[p][0]--;
hungary.cost[0][q]--;
hungary.cost[0][0]--;
for(m=1;m<=hungary.matrixsize;m++)
if(hungary.cost[m][q]==0&&hungary.zeroelem[m][q]==0)
{
hungary.zeroelem[m][q]=2;
hungary.cost[m][0]--;
hungary.cost[0][q]--;
hungary.cost[0][0]--;
}
}
}
for(q=1;q<=hungary.matrixsize;q++)
{
if(hungary.cost[0][q]==1)
{
for(p=1;p<=hungary.matrixsize;p++)
if(hungary.cost[p][q]==0&&hungary.zeroelem[p][q]==0)
break;
hungary.zeroelem[p][q]=1;
hungary.cost[p][q]--;
hungary.cost[0][q]--;
hungary.cost[0][0]--;
for(n=1;n<=hungary.matrixsize;n++)
if(hungary.cost[p][n]==0&&hungary.zeroelem[p][n]==0)
{
hungary.zeroelem[p][n]=2;
hungary.cost[p][0]--;
hungary.cost[0][n]--;
hungary.cost[0][0]--;
}
}
}
}
if(hungary.cost[0][0]>0) //确保hungary.cost[][]中的0元素都在zeroelem[][]中被完全标记出来。
twozero(hungary);
else
judge(hungary,result);
}
hungary=backup;
}
}
}
void refresh(MATRIX &hungary)
{
int i,j,min=0;
int flag1=0,flag2=0;
for(i=1;i<=hungary.matrixsize;i++)
{
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.zeroelem[i][j]==1)
{
hungary.zeroelem[i][0]=1; //有独立零元素
break;
}
}
while(flag1==0)
{
flag1=1;
for(i=1;i<=hungary.matrixsize;i++)
if(hungary.zeroelem[i][0]==0)
{
hungary.zeroelem[i][0]=2;
for(j=1;j<=hungary.matrixsize;j++)
if(hungary.zeroelem[i][j]==2)
{
hungary.zeroelem[0][j]=1;
}
}
for(j=1;j<=hungary.matrixsize;j++)
{
if(hungary.zeroelem[0][j]==1)
{
hungary.zeroelem[0][j]=2;
for(i=1;i<=hungary.matrixsize;i++)
if(hungary.zeroelem[i][j]==1)
{
hungary.zeroelem[i][0]=0;
flag1=0;
}
}
}
} //对打勾的行和列标记成2
for(i=1;i<=hungary.matrixsize;i++)
{
if(hungary.zeroelem[i][0]==2)
{
for(j=1;j<=hungary.matrixsize;j++)
{
if(hungary.zeroelem[0][j]!=2)
if(flag2==0)
{
min=hungary.cost[i][j];
flag2=1;
}
else
{
if(hungary.cost[i][j]<min)
min=hungary.cost[i][j];
}
}
}
} //寻找未被覆盖的最小值
for(i=1;i<=hungary.matrixsize;i++)
{
if(hungary.zeroelem[i][0]==2)
for(j=1;j<=hungary.matrixsize;j++)
hungary.cost[i][j]=hungary.cost[i][j]-min;
}
for(j=1;j<=hungary.matrixsize;j++)
{
if(hungary.zeroelem[0][j]==2)
for(i=1;i<=hungary.matrixsize;i++)
hungary.cost[i][j]=hungary.cost[i][j]+min;
} //未被划线的行减去未被覆盖的最小值,被划线的列加上未被覆盖的最小值
for(i=0;i<=hungary.matrixsize;i++)
for(j=0;j<=hungary.matrixsize;j++)
hungary.zeroelem[i][j]=0; //矩阵清0
circlezero(hungary);
}
void output(int result[5041][2],MATRIX hungary)
{
int num; //解的数量
int minsum; //最小的工作成本
int i,j;
char w;
int start; //每个解的储存开始位置
minsum=0;
for(i=1;i<=hungary.matrixsize;i++)
{
minsum=minsum+hungary.costforout[i][result[i][1]];
}
printf("最优解的目标函数值为%d.\n",minsum);
num=result[0][0];
printf("有%d种解.\n",num);
getchar();
for(i=1;i<=num;i++)
{
printf("按任意键输出第%d种解.\n",i);
scanf("%c",&w);
start=(i-1)*hungary.matrixsize+1;
for(j=start;j<start+hungary.matrixsize;j++)
printf("第%d个人做第%d件工作.\n",result[j][0],result[j][1]);
printf("\n");
}
}
参考
Assignment Problem(任务分配问题) 详解
Cmd Markdown 公式指导手册
十分钟教你求解分配问题
匈牙利算法详解