第二次结对编程作业——毕设导师智能匹配

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\n",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?'\n':' ');
	}

随机化函数

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.开工前问题讨论的细致些,能给后续的工作避免不少麻烦。

posted @ 2016-09-30 14:12  干吧得  阅读(318)  评论(2编辑  收藏  举报