031402402 曹鑫杰
031402428 鄢继仁
第二次结对编程作业——毕设导师智能匹配
项目链接:https://coding.net/u/Shepard_y/p/Homework/git
问题描述:
编码实现一个毕设导师的智能匹配的程序。提供输入包括:30个老师(包含带学生数的要求的上限,单个数值,在[0,8]内),100个学生(包含绩点信息),每个学生有5个导师志愿(志愿的导师可以重复但不能空缺)。实现一个智能自动分配算法,根据输入信息,输出导师和学生间的匹配信息(一个学生只能有一个确认导师,一个导师可以带少于等于其要求的学生数的学生)及未被分配到学生的导师和未被导师选中的学生。
问题分析:
- 算法的目标?
确保最后没分到符合志愿的剩下的学生数越少越好。- 随机的数据,是不是一定要老师所带学生总数数大于学生数?
考虑到实际情况所有学生最后都要分配到导师,所以要求导师所带学生数大于学生总数。- 随机的数据,要不要考虑实际中学生志愿会集中在热门老师的问题?
也是为了符合实际情况,不让数据太水,要加入这个条件。- 要不要固定学生总数和导师总数
为让程序更灵活,不把学生总数和导师总数固定- 选择的语言?
C++- 导师可不可选择0个学生?
有可能导师不想要带学生或因为自身原因无法带学生,我们可以让导师选择带的学生数为0。- 文件输入采用txt,数据库,格式如何?
经过讨论,采用更加熟悉的txt文件输入,格式经讨论后确定,具体可见项目中format.txt。- 学生与老师之间的权重要怎么定义(分配规则,志愿梯度、平行?)
分配规则:采用绩点排名90%+梯度志愿10%。权重分配公式:具体看代码实现部分- 如果一个学生多个志愿重复选同一老师怎么办?
随机生成的数据可能会出现这种情况,考虑过为该学生增加权重,但增加过多是不合理的,过少使得学生浪费了自己的机会。后来发现在实现中,学生重复选择是没有太大意义的。- 变量名命名规范和注释
这次使用的C++,决定采用下划线命名法,注释的话尽量多注释。
算法设计:
算法目标
确保最后没分到符合志愿的剩下的学生数越少越好。
题目分析
学生和导师可以看做一个二分图,导师拆成多个点,数量等于其所期望学生数,每个学生填报的每个志愿就相当于学生与老师之间存在一条边。如果不考虑分配的优先程度(即边的权重)的话,那就是求一个二分图的最大匹配。如果考虑上绩点等多个因素(边有权值),就是求一个二分图的带权匹配。
算法选择
解决二分图的带权匹配可以用KM算法或者费用流。两者差距不大,KM算法在二分图上效率比较高,但却不能再一般图上跑。我们在这里决定采用KM算法。
算法介绍
KM算法解决的是二分图最大权完备匹配,我们给每一个边设定一个权重,KM算法可以得到一个完备匹配(完备匹配一定是最大匹配,所以匹配数最大,即学生选上导师的人数最多)并且权值总和最大(从整体上让大家都满意)。
Kuhn-Munkras算法流程:
(1)初始化可行顶标的值
(2)用匈牙利算法寻找完备匹配
(3)若未找到完备匹配则修改可行顶标的值
(4)重复(2)(3)直到找到相等子图的完备匹配为止
随机化数据设计
为了更贴合实际,在随机化数据的同时也添加了一些条件
1.规定导师所期望学生总数要大于等于学生总数。
2.根据实际情况设定热门、普通、冷门导师,各占比20%、60%、20%,选中概率分别为50%,40%,10%(概率不绝对,具体看随机的数据,但相差幅度不大)。
权重设计
设某一学生绩点排名为Rank(最高1,最低m),选择某一导师为第 i 志愿。
权重 = ( ( ( m+1 - Rank ) / m * 0.4 + 0.6 ) * 0.9 + ( ( 6 - i ) * 2 / 100 ) ) * 10000
即 绩点占比90%( 60%的底分+40%的排名分) + 志愿占比10% ,最高分10000,但最低分也不会太低,让彼此间有差异,但又不会太大。
代码实现:
随机化导师数据
while(true)
{
int sum=0; //导师所带学生总数
memset(tea,0,sizeof(tea));
for(int i=1;i<=n;i++)
{
tea[i].id=i; //导师编号
tea[i].num=r(0,8); //学生人数
sum+=tea[i].num;
}
if(sum>=m) break; //导师所带学生总数应大于学生总数
}
for(int i=1;i<=n;i++)
{
printf("%-8d%-8d
",tea[i].id,tea[i].num);
}
随机化学生数据
for(int i=1;i<=m;i++)
{
stu[i].id=i; //学生编号
stu[i].GPA=((double)r(100,500)/100.0); //绩点
for(int j=1;j<=5;j++)
{
stu[i].aspiration[j]=rand_tea(); //志愿
}
printf("%-8d%-8.2lf",stu[i].id,stu[i].GPA);
for(int j=1;j<=5;j++) printf("%-3d%c",stu[i].aspiration[j],j==5?'
':' ');
}
随机化函数
int r(int x,int y) //随机取[x,y]中的一个数
{
int t=y;
y=x;
x=t-x+1;
return rand()%x+y;
}
int rand_tea() //50%选中热门导师(占比20%),10%选中冷门(20%),40%选中一般(60%)
{
int tmp=r(1,10000);
if(n==1) return 1;
if(tmp<=5000)
{
while(1)
{
tmp=r(1,n);
if(v1[tmp]) return tmp;
}
}
else if(tmp>=9001)
{
while(1)
{
tmp=r(1,n);
if(v2[tmp]) return tmp;
}
}
else
{
while(1)
{
tmp=r(1,n);
if(v3[tmp]) return tmp;
}
}
}
核心算法
bool DFS(int x) //匈牙利算法寻找増广路
{
stu_vis[x] = true;
for(int y = 1; y <= tot; y++)
{
if(tea_vis[y]) continue;
int tmp = stu_l[x] + tea_l[y] - g[x][y];
if(tmp == 0)
{
tea_vis[y] = true;
if(linker[y] == -1 || DFS(linker[y]))
{
linker[y] = x;
return true;
}
}
else if(slack[y] > tmp)
slack[y] = tmp;
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker));
memset(tea_l,0,sizeof(tea_l));
for(int i = 1;i <= stu_num;i++) //设置顶标
{
stu_l[i] = -INF;
for(int j = 1;j <= tot;j++)
{
if(g[i][j] > stu_l[i])
{
stu_l[i] = g[i][j];
}
}
}
for(int x = 1;x <= tot;x++)
{
for(int i = 1;i <= tot;i++) slack[i] = INF;
while(true)
{
memset(stu_vis,false,sizeof(stu_vis));
memset(tea_vis,false,sizeof(tea_vis));
if(DFS(x)) break; //寻找到增广路,进行下一个点的増广
int d = INF;
//修改顶标
for(int i = 1;i <= tot;i++)
{
if(!tea_vis[i] && d > slack[i])
{
d = slack[i];
}
}
for(int i = 1;i <= tot;i++)
{
if(stu_vis[i])
{
stu_l[i] -= d;
}
}
for(int i = 1;i <= tot;i++)
{
if(tea_vis[i]) tea_l[i] += d;
else slack[i] -= d;
}
}
}
int res = 0; //计算总权重
for(int i = 1;i <= tot;i++)
{
if(linker[i] != -1)
{
res += g[linker[i]][i];
}
}
return res;
}
具体代码可见项目链接 https://coding.net/u/Shepard_y/p/Homework/git
结果分析:
下面为程序在一些极端条件下的测试
固定导师数为30,学生数为100,热门导师占20%,6人,冷门导师占20%,6人。
横坐标为学生志愿随机到热门导师的概率,概率越高,学生就越会集中选取热门导师。
纵坐标为测10组数据所得的平均数。
由图可知,随着概率的上升,未分配到学生的导师数量和为分配到导师的学生数量也随着上升。
分析
- 由于学生集中选部分导师,其他导师填报人数很少,甚至没有,这会导致未分配的情况出现的。
- 在非极端情况下,该程序表现情况不错。若是不考虑导师热不热门,采用原版随机化程序,基本上不会出现学生未选上老师的情况。
- 每次只测了10组数据,可能不够客观。
结对感受:
曹鑫杰: 本次作业对友主要负责算法设计,对友编码时,在旁边提出问题和算法可能存在的bug,讨论并排出这些可能,两个人分析问题会更全面一点,对友提出的算法很优,后面在测试的时候我们都比较满意。
鄢继仁: 这一次结对编程,由于一开始我们讨论了许多问题,就很快确定了算法。对友也给了很多建议,所以进行的比较顺利。同时也遇到了不少困难,比如在编码的时候要多注意变量名的规范和多加注释,向对友分析算法的同时由于自己的表达能力不佳,导致交流遇到了障碍。
对彼此结对中的闪光点或建议的分享
1.进行合理的分工,进度会更快些,比如确定好输入格式后,随机化数据和算法实现可以分开来写。
2.开工前问题讨论的细致些,能给后续的工作避免不少麻烦。