04.毕设导师智能分配
031402508 洪佳铭
031402516 黄瑞钰
项目源码:猛戳此处⬅️
问题描述
编码实现一个毕设导师的智能匹配的程序。提供输入包括:30个老师(包含带学生数的要求的上限,单个数值,在[0,8]内),100个学生(包含绩点信息),每个学生有5个导师志愿(志愿的导师可以重复但不能空缺)。实现一个智能自动分配算法,根据输入信息,输出导师和学生间的匹配信息(一个学生只能有一个确认导师,一个导师可以带少于等于其要求的学生数的学生) 及 未被分配到学生的导师 和 未被导师选中的学生。
问题分析
本次算法采用的是Gale-shapley算法来解决配对的问题
-
Gale-shapley算法介绍(摘自《硕士研究生与导师的双向选择的最优匹配》——向冰,刘文君)
-
如何实际应用Gale-shapley算法?
我们发现Gale-shapley算法需要在信息对称且完全的情况下,按照偏好,进行相互选择。这次任务导师相对于学生信息是公开的,学生可以自主根据喜欢的导师进行志愿填写。然而这次没有让导师来选择学生,而是要自动分配学生,所有学生相对于导师信息其实是不公开的,这样就导致双方的信息是不对称的。 所以我们通过学生的绩点高低来排序(若绩点相同,就按照学号大小来排列),这样导师就按照该排序来模拟导师偏好,来进行学生分配。从而就可以满足Gale-shapley算法需要在信息对称且完全的情况下,选择的双方按照自己的偏好进行选择的条件,从而得出最优的匹配。
代码分析
- 随机生成数据
导师的ID、希望带领的学生个数,学生的学号、绩点、五大志愿均随机生成(部分代码如下)
/**
* 自动生成导师数据用于测试
* 格式为: 导师姓名 导师ID 希望带领学生个数
* Demo: 导师01 7561 7
* @param tutorNum
*/
public void createTutorData(int tutorsNum) {
try{
Writer writer = new FileWriter("testData/tutorsData.txt");
BufferedWriter buffWriter=new BufferedWriter(writer);
buffWriter.write("导师姓名" + " "
+ "教师ID" + " "
+ "导师希望带领学生数");
buffWriter.newLine();
for(int i = 1;i <= tutorsNum;i++) {
if(i < 10) {
buffWriter.write("导师0" + i + " "
+ createRandomTutorID() + " "
+ createRandomLeadNum());
} else {
buffWriter.write("导师" + i + " "
+ createRandomTutorID() + " "
+ createRandomLeadNum());
}
buffWriter.newLine();
}
buffWriter.close();
writer.close();
System.out.println("导师测试数据写入成功!");
} catch(IOException e) {
System.out.println("导师测试数据文件写入错误:" + e.getMessage());
}
}
/**
* 自动生成学生数据用于测试
* 格式为: 学生姓名 学号 绩点 第一志愿 第二志愿 第三志愿 第四志愿 第五志愿
* Demo: 学生01 231402001 3.21 1234 2234 1234 1234 1234
* @param studentsNum
*/
public void createStudentData(int studentsNum) {
try{
Writer writer = new FileWriter("testData/studentsData.txt");
BufferedWriter buffWriter = new BufferedWriter(writer);
buffWriter.write("学生姓名" + " "
+ "学生学号" + " "
+ "学生绩点" + " "
+ "第一志愿" + " "
+ "第二志愿" + " "
+ "第三志愿" + " "
+ "第四志愿" + " "
+ "第五志愿");
buffWriter.newLine();
for(int i = 1;i <= studentsNum;i++) {
if(i < 10) {
buffWriter.write("学生00" + i + " "
+ createRandomStudentNum() + " "
+ createRandomGradePoint() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor());
} else if (i < 100){
buffWriter.write("学生0" + i + " "
+ createRandomStudentNum() + " "
+ createRandomGradePoint() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor());
} else {
buffWriter.write("学生" + i + " "
+ createRandomStudentNum() + " "
+ createRandomGradePoint() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor() + " "
+ createRandomTutor());
}
buffWriter.newLine();
}
buffWriter.close();
writer.close();
System.out.println("学生测试数据写入成功!");
} catch(IOException e) {
System.out.println("学生测试数据文件写入错误:" + e.getMessage());
}
}
- 对学生进行排序
将学生按照绩点高低(若绩点相同,按照学号大小)排列
public class SortByGradePoint implements Comparator{
//按绩点从高到低排列,如果绩点相同,则按照学号从小到大排
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
if(((StudentInf)o1).getGradePoint() < ((StudentInf)o2).getGradePoint()) {
return 1;
} else if(((StudentInf)o1).getGradePoint() > ((StudentInf)o2).getGradePoint()) {
return -1;
} else if((((StudentInf)o1).getsNum().compareTo(((StudentInf)o2).getsNum())) < 0){
return 1;
} else {
return -1;
}
}
}
- 核心算法:分配导师
- 根据学生们的志愿一轮一轮的选择自己中意的导师。(如果该学生该轮志愿选的导师,其带领学生人数已经满了,则进行下一个学生的分配。该学生需要等待下一轮志愿再进行分配。)
- 每一轮过后都会有一些学生被分配,当五轮(五个志愿)都结束后,这个算法就结束了。
- 在这个算法中,100个学生共需要进行5轮选择,因此,算法最多100*5轮循环就结束了。
public static void assignmentTutor(HashMap<String,TutorInf> tutorsMap,ArrayList<StudentInf> studentsList,ArrayList<StudentInf> isAssignmentStudentsList) {
for(int i = 0 ;i < 5;i++) {![image](http://note.youdao.com/favicon.ico)
Iterator<StudentInf> iterator = studentsList.iterator();
while (iterator.hasNext()) {
StudentInf studentInfTemp = iterator.next();
TutorInf tutorInftemp = (TutorInf)(tutorsMap.get(studentInfTemp.getTutorsWish().get(i)));
if(tutorInftemp.getLeadedNum() < tutorInftemp.getLeadNum()) {
tutorInftemp.setLeadedNum(tutorInftemp.getLeadedNum() + 1); //导师带领人数+1
tutorInftemp.getLeadStudents().add(studentInfTemp.getsName() + "(" + studentInfTemp.getsNum() + ")"); //学生名字、学号添加到导师带领学生列表
studentInfTemp.setAssignment(true); //标记该学生已分配导师
studentInfTemp.setMyTutorName(tutorInftemp.getTname());//记录该学生的导师姓名
studentInfTemp.setMyTutorID(tutorInftemp.gettID());//记录该学生的导师ID
isAssignmentStudentsList.add(studentInfTemp); //将该学生添加到已将分配的学生List
iterator.remove();
} else if (tutorInftemp.getLeadedNum() == tutorInftemp.getLeadNum()) {
continue;
}
}
}
}
代码测试结果分析
-
自动生成的数据
-
测试结果
-
分析
在选择的双方信息对称的前提下,Gale-shapley算法很好的解决了导师分配的问题。但是还是会有少数学生没有分配到导师。最优情况是所有学生都得到了分配,最坏情况则是有十多个学生未得到分配。
算法优劣分析及后期改进
从算法本身来说,Gale-shapley算法完全可以满足本次问题的要求。后期改进的话我们觉得可以从匹配双方的偏好方面进一步优化,使得分配更加合理,这次导师偏好完全只是依靠学生绩点和学号来模拟的。还有就是从随机数据的产生方面加入更多的限制条件,使得产生的随机数据更加贴近现实。
第二次结对感受
洪佳铭(031402508): 本次的结对任务说实话并不是我的强项,最终磕磕绊绊地完成了基本目标,但是还是暴露出我对编码这方面的不足,这需要我后期投入更多的时间和精力去多多接触。很感谢队友,他承担了很大一部分的任务,果然是中国好队友。
黄瑞钰(031402516):最初看到这个作业的时候,不知道怎么开始动手,前几天没什么进度。后来通过学校图书馆查了一些关于研究生导师选择的论文,发现Gale-Shapley 算法可以很好的解决分配的问题。于是又阅览了《算法的乐趣》关于Gale-Shapley算法的一些应用实例。然而当要开始写代码的时候,还是一脸懵逼。由于好久没写Java,连语法都有点忘记。脑子里已经有思路了,但是就是不知道怎么用代码来实现,然后就又拖了一两天。 后面把整个任务分解成一个一个小问题。从数据的文本输入输出,到类的排序,再到Gale-Shapley算法的实现,不断地百度啊百度,一步一步逐渐完善。 感觉前期的拖延,很大一个原因是因为对大一点、麻烦一点、不熟悉的问题,内心不愿意立马去面对。有点困难被吓到了,从而不知道如何去动手来解决。可是当你把大的问题拆解成小问题,一个一个去解决,真的就不一样了。
本次的亮点
这次最大的亮点是发现了Gale-shapley算法,这个算法很好地解决了我们的问题,为我们提供了很好的思路。
参考资料
- 王晓华。《算法的乐趣》第七章稳定匹配与舞伴问题
- 向冰,刘文君。硕士研究生与导师的双向选择的最优匹配