第二次结对编程作业——导师分配
合作关系:
代码链接:链接到coding.net
一、问题描述
编码实现一个毕设导师的智能匹配的程序。提供输入包括:30个老师(包含带学生数的要求的上限,单个数值,在[0,8]内),100个学生(包含绩点信息),每个学生有5个导师志愿(志愿的导师可以重复但不能空缺)。实现一个智能自动分配算法,根据输入信息,输出导师和学生间的匹配信息(一个学生只能有一个确认导师,一个导师可以带少于等于其要求的学生数的学生)及 未被分配到学生的导师 和 未被导师选中的学生。
要求:
- 输入的数据,另外写生成程序随机实现。
- 为输入输出设计标准化、通用化、可扩展的接口,为该智能匹配程序模块后期可能的整合入系统提供便利。
- 输入输出的格式,如采用文本文件或数据库的方式输入,可自由讨论确定,但需要明确,为后期可能的整合入系统提供便利。
- 需要为智能匹配算法确立几条分配或排序原则,比如 绩点优先、或其他、或其他等等,请你们结对讨论确定。
- 算法评价的目标是:对于同一组输入,输出的未被导师选中的学生数越少越好。
- 代码具有规范性。
- 实现的程序语言不做限制性要求。
二、问题分析
这个题目,我们把重点放在了“分配不到老师的学生最少”上,于是,我们把每个人自己的五个志愿当成平行志愿。为了解决可能出现的五个志愿中可能有相同的志愿,采取的方式是合并相同志愿,于是每个人的五个志愿变成了每个人有1-5个志愿。
这时候,根据学生志愿个数的不同,由于志愿少的学生被导师选中的机会少,所以给他们优先考虑分配。对于学生设置的学分值,我们采取的方式是,无视他。因为我们希望的为分配到导师的学生数最少,而不是成绩最好的学生优先分配导师。这样,虽然不合情理,却更有可能达到题目要求。
在老师选志愿的时候,是通过“老师的期望值-报名学生数”的值来排序,我们认为,报名学生数少的老师应该先选学生,这样他就很有可能尽快地分担掉学生,其他老师的学生数就可以减少,有利于他们的选择。而对于老师的期望值,则是需要越多的老师越先选择,因为他的接受能力比较大。于是,我们认为通过“老师的期望值-报名学生数”的值排序可以比较客观的给出老师分配的合理方案。
三、求解步骤
- 初始化数据,并给学生志愿数进行整理;
- 把老师按照期望值-报名学生数来排序;
- 老师选择学生,在列表里面,优先给志愿数少的学生分配,因为他们机会少;
- 对于已入选的学生,从这位学生的其他志愿的老师列表里面除名;
- 每位老师选择完学生,有可能会淘汰一批学生,把这些学生的志愿数“-1”,这样他在其他志愿老师那边的分配会更优先一点;
- 分配未完则重复步骤2至步骤6;
- 分配完毕则结束;
- 输出结果,程序结束。
四、实现过程与代码分析
由于两人的c语言基础比较薄弱,害怕直面“指针”这个问题,于是乎,选择了用Java来实现。
同时!同时!同时!为了使用方便,直接类里面的数据设置为public!好像是有点坑,但是!好吧有点坑。。。
1.Teacher类
public class Teacher {
int need ;
int had ;
int flag ; //记住当前导师编号
boolean be_distributed ;
int stu[] = new int[100] ;
public Teacher(){
this.be_distributed = false ;
this.need = (int)(Math.random()*8) ;
this.had = 0 ;
}
}
2.Student类
import java.util.Arrays;
public class Student {
double credit ;
int number ; // 志愿数
int want[] = new int[5] ;
int fanal ;
public Student(){
this.credit = Math.random() * 4 + 1 ;
for(int i = 0 ; i < 5 ; i ++ ){
this.want[i] = (int)(Math.random()*29) ;
}
this.number = setNumber() ;
this.fanal = -1 ;
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
public int getNumber() {
return number;
}
public int[] getWant() {
return want;
}
public void setWant(int[] want) {
this.want = want;
}
public int setNumber() {
// TODO Auto-generated method stub
int flag = 1 ;
int j ;
for(int i = 1;i < 5;i++) {
for(j = 0 ; j < flag; j++){
if(want[i] == want[j])
break ;
}
if(flag == j)
want[flag++] = want[i] ;
}
return flag ;
}
@Override
public String toString() {
return "Student [credit=" + credit + ", number=" + number + ", want=" + Arrays.toString(want) + "]";
}
}
3.通过随机数获得学生数据(位于Student类构造方法中)
public Student(){
this.credit = Math.random() * 4 + 1 ;
for(int i = 0 ; i < 5 ; i ++ ){
this.want[i] = (int)(Math.random()*29) ;
}
this.number = setNumber() ;
this.fanal = -1 ;
}
4.学生志愿合并方法(位于Student类中)
通过查询学生的志愿,把相同志愿的合并在一起
public int setNumber() {
// TODO Auto-generated method stub
int flag = 1 ;
int j ;
for(int i = 1;i < 5;i++) {
for(j = 0 ; j < flag; j++){
if(want[i] == want[j])
break ;
}
if(flag == j)
want[flag++] = want[i] ;
}
return flag ;
}
5.将老师按(老师期望值-学生报名数)排序
for(int i = 0 ; i < 30 ; i ++){ //将老师按(老师需要的学生数-学生选择数)排序
Teacher temp = new Teacher() ;
for(int j = i+1 ; j < 30 ; j ++){
if( teacher[i].had > teacher[j].had){
temp = teacher[i] ;
teacher[i] = teacher[j] ;
teacher[j] = temp ;
}else if(teacher[i].had == teacher[j].had){
if(teacher[i].need < teacher[j].need){
temp = teacher[i] ;
teacher[i] = teacher[j] ;
teacher[j] = temp ;
}
}
}
}
6.给报该名老师的学生排序
给该老师分配前,根据学生的志愿值排序,志愿值少的学生优先分配
for(int i = 0 ; i < 30 ; i ++){ //将各个老师对应的学生按所剩志愿个数排序并分配
for(int j = 0 ; j < teacher[i].had ; j ++){ //排序
int temp1 ;
for(int k = j+1 ; k < teacher[i].had ; k ++){
if(student[teacher[i].stu[j]].number > student[teacher[i].stu[k]].number){
temp1 = teacher[i].stu[j] ;
teacher[i].stu[j] = teacher[i].stu[k] ;
teacher[i].stu[k] = temp1 ;
}
}
}
if (teacher[i].need >= teacher[i].had) { // 分配
for (int j = 0; j < teacher[i].had; j++) {
teacher[i].be_distributed = true ;
student[teacher[i].stu[j]].fanal = teacher[i].flag;
for (int m = i+1; m < 30; m++) {
int f = teacher[m].had;
for (int n = 0; n < f; n++) {
if (teacher[m].stu[n] == teacher[i].stu[j]) {
teacher[m].stu[n] = teacher[m].stu[--teacher[m].had];
}
}
}
}
} else {
for (int j = 0; j < teacher[i].need; j++) {
teacher[i].be_distributed = true ;
student[teacher[i].stu[j]].fanal = teacher[i].flag;
for (int m = i + 1; m < 30; m++) {
int f = teacher[m].had;
for (int n = 0; n < f; n++) {
if (teacher[m].stu[n] == teacher[i].stu[j]) {
teacher[m].stu[n] = teacher[m].stu[--teacher[m].had];
}
}
}
}
for (int j = teacher[i].need; j < teacher[i].had; j++) {
student[teacher[i].stu[j]].number--; // 志愿选了此老师,此老师却未选该学生,则将该学生志愿减一
}
}
for(int l = i + 1 ; l < 30 ; l ++){ //将老师按学生选择多少排序
Teacher temp = new Teacher() ;
for(int j = l+1 ; j < 30 ; j ++){
if(teacher[l].had > teacher[j].had){
temp = teacher[l] ;
teacher[l] = teacher[j] ;
teacher[j] = temp ;
}else if(teacher[l].had == teacher[j].had){
if(teacher[l].need < teacher[j].need){
temp = teacher[l] ;
teacher[l] = teacher[j] ;
teacher[j] = temp ;
}
}
}
}
}
五、结果分析(修改于2016-10-08 21:12)
经过多次测试,有很大的概率可以给所有的学生分配到导师。
少概率出现有个位数的学生分配不到老师,这种情况下,有分为导师需要的总学生人数少于100,和学生志愿冲突过多两种情况。
总的来说,结果比较满意。
我是分割线=应要求,结果添加数据分析=====
评估方式采集了老师需要的学生数和最后未分配的学生数,由于老师需要的学生数是随机数产生,故存在老师需要的学生总数可能小于学生总数100的情况,这时候就不能单单以未分配的学生数来评价算法优劣了。于是分成了以下两种情况:
- 老师需要的学生大于100,存在学生完全分配和学生未分配;
- 老师需要的学生小于100,存在学生分配人数等于老师需要的学生数和分配人数小于需要的学生数。
那么设置一个评估值,计算公式为:
- 评估值=((100-需要数)>0)×(100-需要数-未分配)+((100-需要数)<=0)×未分配数
- 我觉得这个公式的设计挺巧的
计算出的结果分三种:
- 评估值大于0,则需要数大于100,且有学生未分配,评估值越大,结果越糟糕;
- 评估值等于0,需要数大等于分配数,即可能需要数大于100且学生完全分配,或者需要数小于100,但学生已经根据需要数完全分配了,评估值等于0越多说明分配越有效;
- 评估值小于0,需要数小于100,值越小,分配结果越差。
以下是连续100次的分配结果:
统计结果:
- 完全分配次数51次,占51%;
- 需要数大于100还无法完全分配次数为19次,占19%;
- 需要数少于100且不完全分配次数30次,占30%。
评估结果如上。
但是,这个评估策略有一定的局限性,因为学生报的志愿也是随机数产生的,这就意味着他们的志愿并不“完美”,举个极端的例子,所有的学生都报了1-5号老师,这样的话其他老师需要的学生数再多也是没用的,所以此评估策略不能很全面地评估算法的结果,只能作为一个分析方式参考参考。如果要问“那为什么不通过考虑所有学生的志愿情况进行结果分析呢”,那么我想说,如果我可以通过算法评价志愿的分布,那么我就可以对学生志愿进行完全匹配了,我就不需要上面这个算法了。
六、小结
先抛出结论,结对编程确实还不错。
从一开始,两个人讨论方案根本是毫无思绪,这是候,旁边的一组已经敲了上一百行代码了,表示好为难,于是两个人想啊分析啊,讨论啊,终于雏形出来了,然后各种举例啊优化啊,一个提出想法,一个找漏洞,推敲来推敲去,这样结对的优点就出来了,互补对方的盲点。最终,定下了方案。
编程环节,可以说是困难重重,刚入手,连数据的定义都成问题啊!还好有度娘啊!磕磕绊绊总算写完了,结果,出现了预期之外的结果:第一学生总是选了一个他没报的老师!?什么鬼,怎么可能出现这种低级错误??而且数据中每次有近10个学生分配不到导师。分配不到导师就算了,可能算法差吧,可是分配了错误的导师可是大问题啊。于是检查检查检查再检查,然而压根没有头绪,查来查去都没错啊。没办法,从头开始慢慢找。还是没错!怎么办,输出内容吧。然后再满屏的数据中去分析。结果是,数组下标引用错误了!“have++”和“++have”的差别你敢信!?不说了,说多了都是泪。这才终于是完成了,可是苦了我俩了。
同时,代码还是有很多的不足的。一是并没有符合要求写标准的接口;二是代码的设计方面,写了公开的类一点也不好,而且学生和老师数据是全局变量看起来有点坑;三是代码的优化太少,尽管对算法优化了一点,但是对执行速度来说简直没有任何优化,循环语句有点多,比较不完美。
对了,还有一件烦恼的事就是git,真是心累啊。
==割,编辑于2016-10-08 21:38
七、优化方案(附加)
由于题目的要求是未分配学生数尽可能少,以至于我们直接无视学生的绩点,虽然这有利于完成题目,但是不适合现实的导师分配条件。然而其实我们是有优化的方案的,可以保证绩点高的学生更有希望分配到导师。但是由于对于未分配的学生数并没有影响,故没有执行。
优化策略:
- 先按上面的方式分配完导师;
- 找到未分配到导师的学生,按照绩点从高到低排序;
- 找到绩点最高的学生的所有志愿老师,把这些老师的学生中绩点最低的淘汰掉,把这位绩点比较高的学生填上这个位置;
- 淘汰掉的这位学生根据绩点高低加入未分配的学生队列;
- 重复3到4,知道所有的都优化完毕。
这样,虽然未分配的学生数没有变化,但是绩点更高的更有希望分配到导师,显然更符合现实条件更合理。
顺便提一句,markdown要输入*,需要输入\*。