毕设导师智能匹配
结对小组:肖承志 031402327
吴宇轩 031402330
一、问题描述
编码实现一个毕设导师的智能匹配的程序。提供输入包括:30个老师(包含带学生数的要求的上限,单个数值,在[0,8]内),100个学生(包含绩点信息),每个学生有5个导师志愿(志愿的导师可以重复但不能空缺)。实现一个智能自动分配算法,根据输入信息,输出导师和学生间的匹配信息(一个学生只能有一个确认导师,一个导师可以带少于等于其要求的学生数的学生) 及 未被分配到学生的导师 和 未被导师选中的学生。
二、问题分析
- 需求:智能分配导师,未被导师选中的学生数越少越好
- 编程语言:C++
- 数据输入:文本文件
- 数据输出:文本文件
- 学生五个志愿是否必须填满: 必须填满
- 学生五个志愿是否可以重复:可以重复
- 绩点的意义:绩点只作为算法优化而不作为核心匹配原则,尽量让绩点高的学生在尽可能多的学生被选上的情况下与其较高志愿的导师匹配成功
三、算法设计
程序算法评价的目标是:对于同一组输入,输出的未被导师选中的学生数越少越好。绩点靠前学生先考虑,志愿顺序考虑,多次循环比较。
一开始我们选择按绩点排序,从绩点第一的A学生的第一志愿导师开始,依次往下进行与导师的匹配,若A学生的第一志愿导师仍有空余名额,则将其配对并,然后对绩点第二的B学生的第一志愿导师进行匹配,若B学生的第一志愿导师没有空余名额,则将其与第二志愿导师进行匹配,依次类推,若某学生的五个志愿导师都没有空余名额,则将其移入未被导师选中的学生名单中,接着开始下一个同学的匹配。
算法分析:该算法的分配排序原则是绩点志愿优先原则。按照学生的绩点进行排序,这种算法能让绩点越高的学生的导师选择权越高,比较满足高绩点学生的高志愿,但是容易让许多绩点低的学生因为五个志愿导师都被前面学生选择走了导致第一轮志愿匹配失败,从而产生大量未被导师选中的学生。
为了使更多学生能够在较少的志愿填报轮数中选到自己志愿的导师,我们改良了算法的分配排序原则,采用低热度优先原则。我们先确定了一个导师热门度的定义,导师热门度=选择该导师的学生总数/该导师学生数的上限
,另外定义了一个学生志愿导师平均热度=该学生志愿的导师的热门度总和/志愿数
(可能有些学生在对他进行匹配时他所选的导师有名额已满的情况,这时候他的志愿数就减少为他志愿的导师中还有名额的导师的数量)
改良后的算法根据两个原则进行匹配:
- 低热度优先算法(第一原则)
热门度高的导师因为选择他的学生总数相比其他导师来说远大于他所带的学生数量,因此如果先考虑他,很大可能出现在他所带学生名额全满以后还有学生选他的情况,而该学生的其他志愿如果同为高热度导师,本身绩点不够高,优先度偏低,就会在第一轮志愿匹配中无法中选。
从热门度低的导师开始,先将有志愿是他的学生与他进行匹配,匹配成功后将该学生的其余四个志愿导师的 选择该导师的学生总数减一(降低其他导师热度),动态调整之后,继续进行下轮匹配。
原则一:低热度导师先匹配
原则二: 同一导师,学生志愿导师平均热度较高的先匹配算法分析:低热度的导师由于选他的学生总数和该导师所带学生数名额相差相对较小甚至少于该导师所带学生数名额,将有志愿是该导师的学生与他先匹配,这样可以提升导师的资源利用率,同时能降低该学生其他志愿导师的热度,而对于同一个导师来说,学生A的志愿导师平均热度如果高于学生B的志愿导师平均热度,说明学生A的志愿导师相对B来说总体更热门,因而学生A相对B来说更容易出现志愿导师与其他学生冲突难以选上的情况,所以先匹配学生A。
缺点:大部分低热度导师的志愿次序都是比较靠后的,因而存在一部分同学匹配到的导师在他志愿导师的次序中是比较靠后的。现在我们用第二种算法进行优化。
- 绩点排序优化算法(第二原则)
按照学生的绩点进行排序,由绩点第一的学生A开始,依次查看学生A相对现在已匹配志愿导师B的更高志愿导师C是否有剩余名额,若有则将学生A调整到导师C的名下,每次调整之后,若存在未匹配成功的学生,则对其志愿导师查看有无名额空出,有则进行匹配。
算法分析:按照绩点排序进行优化后,在满足较少的志愿填报轮数中让学生们选到自己志愿导师的情况下,绩点高的学生匹配到的志愿导师次序较前,并在匹配调整中尽量减少未被导师选中的学生数。
四、程序流程
-
随机数据生成程序
Data.cpp
核心部分。输入学生人数和导师人数,输出生成的随机数据,包括学生人数,导师人数,各学生的学号、绩点和志愿,各导师的学生数上限。
void Stu(Student *stu, int stuSum, int teaSum){ //随机生成学生信息并输出
srand((int)time(0));
for(int i = 0; i < stuSum; i++){
stu[i].sno = i + 100000;
stu[i].gpa = (rand() % 5000) / 1000.0;
for(int j = 0; j < 5; j++){
stu[i].stuChoose[j] = rand() % teaSum;
}
}
for(int i = 0; i < stuSum; i++){
printf("%d %.5lf\n%d", stu[i].sno, stu[i].gpa, stu[i].stuChoose[0]);
for(int j = 1; j < 5; j++){
printf(" %d", stu[i].stuChoose[j]);
}
printf("\n");
}
}
void Tea(Teacher *tea, int stuSum, int teaSum){ //随机生成导师信息并输出
int count = 0;
srand((int)time(0));
for(int i = 0; i < teaSum; i++){
tea[i].stuLimit = rand() % 9;
if(tea[i].stuLimit == 0)tea[i].stuLimit++;
count += tea[i].stuLimit;
}
while(count <= stuSum){ //让导师的学生总上限大于学生总人数
int tmp = rand() % teaSum;
if(tea[tmp].stuLimit < 8){
tea[tmp].stuLimit++;
count++;
}
}
for(int i = 0; i < teaSum; i++){
printf("%d\n", tea[i].stuLimit);
}
}
主程序
StuAdv.cpp
-
首先设计学生类和导师类的结构。
class Student{
public:
Student();
bool YN(int tno);
int GetSno();
friend void AddStu(Teacher *tea, Student *stu, int stuSum);
friend void StuInit(Student *stu, int stuSum);
friend void AddTeaHot(Teacher *tea, Student *stu, int stuSum);
friend int cmpgpa(const void *a, const void *b);
friend int cmpsno(const void *a, const void *b);
friend void StuAdv(Teacher *tea, int teaSum, Student *stu, int stuSum);
friend void Optimize(Teacher *tea, Student *stu, int stuSum);
friend void Cut(Student *stu, int sno, int tno);
friend void PutOut(Teacher *tea, int teaSum, Student *stu, int stuSum);
private:
double hotAve; //平均热度
int left; //志愿导师中还学生未选满的导师数
int sno; //学号
int teacher; //中选导师的序号
double gpa; //绩点
vector<int> stuChoose; //学生志愿,去重
int stuFirst[5]; //学生志愿,未去重
bool chooseYN; //是否中选
};
class Teacher{
public:
Teacher();
void TeaAddStu(int stuId);
double GetHot();
bool GetLeft(int sno);
void Cut(Student *stu, int sno, int tno);
friend void TeaInit(Teacher *tea, int teaSum);
friend void CountTeaHot(Teacher *tea, int teaSum);
friend void StuAdv(Teacher *tea, int teaSum, Student *stu, int stuSum);
friend void PutOut(Teacher *tea, int teaSum, Student *stu, int stuSum);
private:
int stuLimit; //导师的学生数上限
int stuSums; //选择该导师但还未中选的学生数
int stuResult; //中选该导师的学生数
double hot; //导师热度
vector<int> teaStuCho; //选择该导师但还未中选的学生列表
vector<int> stuList; //中选该导师的学生列表
bool visited; //匹配过程中是否已经匹配过
};
Student::Student(){
left = 0;
hotAve = 0; //平均热度初始化为0
chooseYN = false; //初始化为未中选
}
Teacher::Teacher(){
stuSums = 0;
stuResult = 0;
teaStuCho.clear();
stuList.clear();
visited = false; //初始化为未匹配
}
-
绩点排序后,初始化导师的热度以及学生的导师平均热度。
void AddStu(Teacher *tea, Student *stu, int stuSum){ //将学生序号加入到各志愿导师下的列表中
for(int i = 0; i < stuSum; i++){
for(int j = 0; j < stu[i].left; j++){
tea[stu[i].stuChoose[j]].TeaAddStu(i);
}
}
}
void CountTeaHot(Teacher *tea, int teaSum){ //计算导师热度
for(int i = 0; i < teaSum; i++){
tea[i].hot = (tea[i].stuSums * 1.0) / (tea[i].stuLimit * 1.0);
}
}
void AddTeaHot(Teacher *tea, Student *stu, int stuSum){ //计算学生所选导师的平均热度
for(int i = 0; i < stuSum; i++){
for(int j = 0; j < stu[i].left; j++){
stu[i].hotAve += tea[stu[i].stuChoose[j]].GetHot();
}
stu[i].hotAve = stu[i].hotAve / (stu[i].left * 1.0);
}
}
-
程序主函数
StuAdv(tea, teaSum, stu, stuSum)
(低热度导师优先匹配)。首先找到热度最低的导师,在选择该导师的学生列表中按平均热度由高到低进行匹配,匹配的同时,删除该学生在其他导师下的选择记录,动态更新其他导师的热度和其他导师的学生的平均热度。循环匹配直至结束。
void StuAdv(Teacher *tea, int teaSum, Student *stu, int stuSum){ //按低热度优先开始匹配
int tmpTea = teaSum;
while(tmpTea--){
int minHot = -1; //热度最低的导师序号
for(int i = 0; i < teaSum; i++){
if(tea[i].visited == false && (minHot == -1 || tea[i].hot < tea[minHot].hot)){
minHot = i;
}
}
tea[minHot].visited = true;
int tmp = min(tea[minHot].stuLimit, tea[minHot].stuSums); //中选该导师的学生数
while(tmp--){
int maxId = 0; //平均热度最高的学生在该导师学生数组中的下标
int maxHotSum = stu[tea[minHot].teaStuCho[maxId]].hotAve; //平均热度最高的学生的平均热度
int getHotSum; //平均热度临时值
for(int i = 1; i < tea[minHot].teaStuCho.size(); i++){
getHotSum = stu[tea[minHot].teaStuCho[i]].hotAve;
if(getHotSum > maxHotSum){
maxHotSum = getHotSum;
maxId = i;
}
}
int maxHotStu = tea[minHot].teaStuCho[maxId]; //当前平均热度最高的学生序号
tea[minHot].stuResult++;
tea[minHot].stuList.push_back(stu[maxHotStu].sno);
tea[minHot].stuSums--;
tea[minHot].teaStuCho.erase(tea[minHot].teaStuCho.begin() + maxId);
stu[maxHotStu].teacher = minHot;
stu[maxHotStu].chooseYN = true;
for(int i = 0; i < stu[maxHotStu].left; i++){
for(int j = 0; j < tea[stu[maxHotStu].stuChoose[i]].teaStuCho.size(); j++){
if(maxHotStu != tea[stu[maxHotStu].stuChoose[i]].teaStuCho[j]){
int tag = tea[stu[maxHotStu].stuChoose[i]].teaStuCho[j];
stu[tag].hotAve = (stu[tag].hotAve * (stu[tag].left * 1.0) - 1) / (stu[tag].left * 1.0);
}
}
int tag = stu[maxHotStu].stuChoose[i];
if(minHot != tag){
tea[tag].stuSums--;
tea[tag].hot = (tea[tag].stuSums * 1.0) / (tea[tag].stuLimit * 1.0);
for(int j = 0; j < tea[tag].teaStuCho.size(); j++){
if(maxHotStu == tea[tag].teaStuCho[j]){
tea[tag].teaStuCho.erase(tea[tag].teaStuCho.begin() + j);
break;
}
}
}
}
}
for(int i = 0; i < tea[minHot].teaStuCho.size(); i++){
int tag = tea[minHot].teaStuCho[i];
stu[tag].hotAve = (stu[tag].hotAve * (stu[tag].left * 1.0)) / ((stu[tag].left * 1.0) - 1.0);
for(int j = 0; j < stu[tag].left; j++){ /*bug所在*/
if(stu[tag].stuChoose[j] == minHot){
stu[tag].stuChoose.erase(stu[tag].stuChoose.begin() + j);
break;
}
}
stu[tag].left--;
}
}
}
-
写主函数StuAdv的时候,因为细节问题两个人都没有发现BUG,结果导致运行的时候程序奔溃,最后我们采用人工手算来演示匹配算法。。。找出BUG所在。。。
-
绩点排序优化。按绩点顺序遍历学生,每名学生按其志愿顺序查看导师的名额是否还有剩余,有则调整其志愿至最高志愿,同时,若有调整,原先所选导师必然空出一个名额,此时若该导师未中选学生列表中存在未分配学生,将其与该导师匹配,检测能否成功,以减少未匹配人数。
void Optimize(Teacher *tea, Student *stu, int stuSum){ //按绩点顺序优化
for(int i = 0; i < stuSum; i++){
if(stu[i].chooseYN == false)continue;
for(int j = 0; j < 5; j++){
if(stu[i].teacher == stu[i].stuFirst[j])break;
else if(tea[stu[i].stuFirst[j]].GetLeft(stu[i].sno)){
tea[stu[i].teacher].Cut(stu, stu[i].sno, stu[i].teacher);
stu[i].teacher = stu[i].stuFirst[j];
break;
}
}
}
}
-
输出函数
void PutOut(Teacher *tea, int teaSum, Student *stu, int stuSum){ //输出
vector<int> stuLeft;
vector<int> teaLeft;
printf("/*学生匹配结果*/\n");
qsort(stu, stuSum, sizeof(stu[0]), cmpsno);
for(int i = 0; i < stuSum; i++){
if(stu[i].chooseYN)printf("学号为%d的学生的导师序号: %d\n", stu[i].sno, stu[i].teacher);
else{
printf("学号为%d的学生的导师序号: 未中选\n", stu[i].sno);
stuLeft.push_back(stu[i].sno);
}
}
printf("\n/*导师匹配结果*/\n");
for(int i = 0; i < teaSum; i++){
if(tea[i].stuResult == 0){
printf("序号为%d的导师未分配学生\n", i);
teaLeft.push_back(i);
}
else{
printf("序号为%d的导师总共分配%d名学生:", i, tea[i].stuResult);
for(int j = 0; j < tea[i].stuList.size(); j++){
printf(" %d", tea[i].stuList[j]);
}
printf("\n");
}
}
if(stuLeft.size() == 0)printf("\n所有学生都已中选导师\n");
else{
printf("\n未被导师选中的学生:");
for(int i = 0; i < stuLeft.size(); i++){
printf(" %d",stuLeft[i]);
}
printf("\n");
}
if(teaLeft.size() == 0)printf("\n所有导师都已分配到学生\n");
else{
printf("\n未被分配到学生的导师:");
for(int i = 0; i < teaLeft.size(); i++){
printf(" %d",teaLeft[i]);
}
printf("\n");
}
}
-
生成的随机数据
DataRE.in
100 30
100000 1.07600
11 10 6 23 7
100001 1.74300
10 9 20 9 25
100002 0.56200
2 3 9 22 0
100003 0.34200
13 24 1 17 10
100004 2.46400
18 15 8 27 13
100005 3.41600
23 5 20 2 17
100006 2.75000
18 25 14 14 8
100007 3.72600
15 13 12 1 14
100008 3.92700
28 15 18 6 25
100009 1.20000
21 0 2 15 7
100010 3.70400
4 8 0 12 18
100011 3.38500
23 12 15 24 27
100012 1.25500
2 20 6 27 17
100013 0.30600
13 5 25 12 14
100014 3.35200
23 6 22 27 21
100015 4.47100
1 2 6 18 29
100016 0.81200
8 7 13 0 15
100017 3.14400
3 3 0 13 2
100018 4.16300
0 22 10 27 29
100019 2.56100
13 13 13 13 26
100020 1.18800
11 17 28 16 4
100021 4.14800
21 7 11 18 9
100022 1.32300
25 22 8 0 1
100023 4.38100
6 27 7 26 20
100024 0.20200
8 26 13 3 20
100025 1.39000
22 18 2 15 0
100026 1.46700
29 4 8 28 22
100027 1.46000
15 16 24 13 19
100028 3.67700
8 18 25 8 10
100029 0.67400
29 4 16 3 22
100030 2.12500
15 29 18 10 12
100031 4.04600
0 0 19 8 16
100032 1.16800
21 21 10 28 24
100033 2.56900
24 9 26 2 21
100034 0.87800
9 21 1 0 14
100035 1.33900
9 5 23 12 6
100036 0.69700
5 19 20 22 12
100037 4.69400
10 8 26 24 26
100038 1.29500
12 1 17 27 11
100039 1.36900
10 22 29 3 21
100040 0.44400
23 27 16 23 26
100041 3.64100
18 9 9 4 10
100042 3.67600
5 28 28 17 24
100043 1.03500
18 9 5 11 18
100044 2.83200
27 11 15 0 14
100045 3.75700
18 24 10 17 13
100046 4.18300
26 16 9 15 9
100047 3.09400
29 9 19 24 21
100048 4.94300
0 9 8 26 25
100049 4.49100
22 23 8 29 26
100050 3.14700
27 3 28 27 1
100051 3.32400
28 15 6 1 17
100052 1.33600
0 25 3 13 27
100053 1.83100
15 13 14 9 28
100054 0.78700
14 11 0 17 7
100055 2.31500
15 26 25 4 5
100056 1.58200
1 10 15 18 25
100057 1.60800
12 19 19 7 29
100058 0.93900
3 17 16 27 7
100059 1.98100
3 6 1 5 18
100060 4.57600
16 17 19 5 0
100061 1.89800
1 15 18 21 25
100062 4.96400
26 15 19 3 14
100063 3.31200
29 9 9 6 21
100064 1.49600
28 23 15 3 15
100065 1.21400
26 13 5 15 6
100066 0.28200
23 25 18 28 5
100067 2.14900
27 5 0 6 27
100068 4.47100
13 0 11 5 5
100069 0.16600
10 20 9 13 12
100070 0.32800
8 4 2 24 3
100071 3.52300
0 6 9 17 4
100072 4.87000
13 10 24 5 1
100073 1.24400
18 22 1 23 26
100074 1.27200
3 7 27 29 7
100075 0.61900
3 0 17 2 25
100076 1.32500
0 18 0 13 18
100077 1.97000
3 21 14 24 18
100078 0.08500
3 3 20 7 5
100079 3.37900
28 23 15 17 24
100080 0.67700
3 6 0 9 7
100081 0.64100
24 21 25 9 3
100082 1.23600
11 28 18 8 15
100083 2.35800
5 6 15 20 2
100084 0.26300
0 15 15 12 9
100085 2.14000
16 29 1 25 29
100086 4.54800
21 23 21 25 27
100087 0.51200
5 12 9 17 18
100088 4.28500
8 2 20 23 24
100089 1.86800
4 20 26 7 10
100090 2.86800
29 21 4 1 4
100091 0.25200
13 27 21 16 14
100092 0.44600
4 18 26 12 14
100093 4.21200
24 22 22 21 25
100094 1.49900
22 9 18 21 27
100095 0.65600
1 23 7 2 6
100096 1.12500
13 22 3 1 5
100097 0.74000
5 29 3 5 4
100098 2.90300
3 13 24 21 2
100099 0.47800
26 10 26 21 4
2
2
4
1
2
1
6
4
1
8
1
1
1
2
6
6
7
1
6
4
1
1
5
7
4
1
6
5
1
7
-
程序运行输出结果
Result.out
(输入数据随机生成)
/*学生匹配结果*/
学号为100000的学生的导师序号: 11
学号为100001的学生的导师序号: 9
学号为100002的学生的导师序号: 9
学号为100003的学生的导师序号: 1
学号为100004的学生的导师序号: 15
学号为100005的学生的导师序号: 23
学号为100006的学生的导师序号: 14
学号为100007的学生的导师序号: 14
学号为100008的学生的导师序号: 28
学号为100009的学生的导师序号: 15
学号为100010的学生的导师序号: 4
学号为100011的学生的导师序号: 23
学号为100012的学生的导师序号: 2
学号为100013的学生的导师序号: 14
学号为100014的学生的导师序号: 6
学号为100015的学生的导师序号: 1
学号为100016的学生的导师序号: 7
学号为100017的学生的导师序号: 3
学号为100018的学生的导师序号: 22
学号为100019的学生的导师序号: 26
学号为100020的学生的导师序号: 16
学号为100021的学生的导师序号: 18
学号为100022的学生的导师序号: 22
学号为100023的学生的导师序号: 27
学号为100024的学生的导师序号: 26
学号为100025的学生的导师序号: 18
学号为100026的学生的导师序号: 29
学号为100027的学生的导师序号: 24
学号为100028的学生的导师序号: 18
学号为100029的学生的导师序号: 16
学号为100030的学生的导师序号: 29
学号为100031的学生的导师序号: 16
学号为100032的学生的导师序号: 24
学号为100033的学生的导师序号: 24
学号为100034的学生的导师序号: 14
学号为100035的学生的导师序号: 23
学号为100036的学生的导师序号: 19
学号为100037的学生的导师序号: 10
学号为100038的学生的导师序号: 17
学号为100039的学生的导师序号: 29
学号为100040的学生的导师序号: 27
学号为100041的学生的导师序号: 9
学号为100042的学生的导师序号: 5
学号为100043的学生的导师序号: 9
学号为100044的学生的导师序号: 15
学号为100045的学生的导师序号: 18
学号为100046的学生的导师序号: 26
学号为100047的学生的导师序号: 29
学号为100048的学生的导师序号: 9
学号为100049的学生的导师序号: 23
学号为100050的学生的导师序号: 27
学号为100051的学生的导师序号: 15
学号为100052的学生的导师序号: 25
学号为100053的学生的导师序号: 13
学号为100054的学生的导师序号: 14
学号为100055的学生的导师序号: 26
学号为100056的学生的导师序号: 15
学号为100057的学生的导师序号: 19
学号为100058的学生的导师序号: 16
学号为100059的学生的导师序号: 6
学号为100060的学生的导师序号: 16
学号为100061的学生的导师序号: 15
学号为100062的学生的导师序号: 19
学号为100063的学生的导师序号: 29
学号为100064的学生的导师序号: 23
学号为100065的学生的导师序号: 26
学号为100066的学生的导师序号: 23
学号为100067的学生的导师序号: 6
学号为100068的学生的导师序号: 0
学号为100069的学生的导师序号: 20
学号为100070的学生的导师序号: 2
学号为100071的学生的导师序号: 0
学号为100072的学生的导师序号: 13
学号为100073的学生的导师序号: 18
学号为100074的学生的导师序号: 7
学号为100075的学生的导师序号: 2
学号为100076的学生的导师序号: 18
学号为100077的学生的导师序号: 14
学号为100078的学生的导师序号: 7
学号为100079的学生的导师序号: 23
学号为100080的学生的导师序号: 6
学号为100081的学生的导师序号: 9
学号为100082的学生的导师序号: 18
学号为100083的学生的导师序号: 6
学号为100084的学生的导师序号: 12
学号为100085的学生的导师序号: 16
学号为100086的学生的导师序号: 21
学号为100087的学生的导师序号: 9
学号为100088的学生的导师序号: 8
学号为100089的学生的导师序号: 4
学号为100090的学生的导师序号: 29
学号为100091的学生的导师序号: 27
学号为100092的学生的导师序号: 26
学号为100093的学生的导师序号: 24
学号为100094的学生的导师序号: 22
学号为100095的学生的导师序号: 1
学号为100096的学生的导师序号: 22
学号为100097的学生的导师序号: 29
学号为100098的学生的导师序号: 2
学号为100099的学生的导师序号: 26
/*导师匹配结果*/
序号为0的导师总共分配2名学生: 100068 100071
序号为1的导师总共分配2名学生: 100003 100015
序号为2的导师总共分配4名学生: 100075 100098 100070 100012
序号为3的导师总共分配1名学生: 100017
序号为4的导师总共分配2名学生: 100010 100089
序号为5的导师总共分配1名学生: 100042
序号为6的导师总共分配5名学生: 100059 100083 100067 100014 100080
序号为7的导师总共分配3名学生: 100078 100016 100074
序号为8的导师总共分配1名学生: 100088
序号为9的导师总共分配7名学生: 100001 100081 100087 100048 100043 100041 100002
序号为10的导师总共分配1名学生: 100037
序号为11的导师总共分配1名学生: 100000
序号为12的导师总共分配1名学生: 100084
序号为13的导师总共分配2名学生: 100053 100072
序号为14的导师总共分配6名学生: 100013 100006 100077 100054 100007 100034
序号为15的导师总共分配6名学生: 100056 100061 100004 100044 100051 100009
序号为16的导师总共分配6名学生: 100060 100020 100058 100031 100085 100029
序号为17的导师总共分配1名学生: 100038
序号为18的导师总共分配6名学生: 100028 100045 100076 100073 100021 100082
序号为19的导师总共分配3名学生: 100036 100062 100057
序号为20的导师总共分配1名学生: 100069
序号为21的导师总共分配1名学生: 100086
序号为22的导师总共分配4名学生: 100096 100022 100018 100094
序号为23的导师总共分配7名学生: 100005 100066 100064 100035 100079 100049 100011
序号为24的导师总共分配4名学生: 100032 100033 100027 100093
序号为25的导师总共分配1名学生: 100052
序号为26的导师总共分配6名学生: 100099 100055 100019 100065 100092 100024
序号为27的导师总共分配4名学生: 100050 100040 100023 100091
序号为28的导师总共分配1名学生: 100008
序号为29的导师总共分配7名学生: 100039 100097 100090 100030 100026 100063 100047
所有学生都已中选导师
所有导师都已分配到学生
五、闪光点(效率分析)
为了分析匹配算法的效率,在源程序的基础上设计出效率分析程序·analysis.cpp·,效率分析程序输入三个值,分别为学生总数、导师总数以及生成的随机数据的组数。根据输入生成多组不同的随机数据,依次运行并记录结果,输出每组数据的匹配效率(匹配学生数/学生总数)和平均志愿水平(每个已匹配学生的导师志愿位置的和/匹配学生数)。程序修改部分如下:
-
将随机数据生成代码段整合到源程序,并初始化学生和导师数组的各个数据。
void StuInit(int teaSum, Student *stu, int stuSum, int seed){ //录入学生信息
srand((int)time(0));
while(seed--){
for(int i = 0; i < 100; i++){
rand();
}
}
for(int i = 0; i < stuSum; i++){
stu[i].left = 0;
stu[i].hotAve = 0; //平均热度初始化为0
stu[i].chooseYN = false; //初始化为未中选
stu[i].sno = i + 100000;
stu[i].gpa = ((rand() + seed) % 5000) / 1000.0;
stu[i].stuChoose.clear();
for(int j = 0; j < 5; j++){
stu[i].stuFirst[j] = (rand()) % teaSum;
stu[i].left++;
for(int k = 0; k < stu[i].stuChoose.size(); k++){
if(stu[i].stuFirst[j] == stu[i].stuChoose[k]){
stu[i].left--;
break;
}
}
if(stu[i].left > stu[i].stuChoose.size()){
stu[i].stuChoose.push_back(stu[i].stuFirst[j]);
}
}
}
}
void TeaInit(Teacher *tea, int teaSum, int stuSum, int seed){ //录入导师信息
int count = 0;
srand((int)time(0));
while(seed--){
for(int i = 0; i < 100; i++){
rand();
}
}
for(int i = 0; i < teaSum; i++){
tea[i].stuSums = 0;
tea[i].stuResult = 0;
tea[i].teaStuCho.clear();
tea[i].stuList.clear();
tea[i].visited = false; //初始化为未匹配
tea[i].stuLimit = (rand()) % 9;
count += tea[i].stuLimit;
}
while(count <= stuSum){ //让导师的学生总上限大于学生总人数
int tmp = (rand()) % teaSum;
if(tea[tmp].stuLimit < 8){
tea[tmp].stuLimit++;
count++;
}
}
}
-
输出函数
void PutOut(Teacher *tea, int teaSum, Student *stu, int stuSum){ //输出
int stuLeft = 0;
int ChoSum = 0;
for(int i = 0; i < stuSum; i++){
if(stu[i].chooseYN){
for(int j = 0; j < 5; j++){
if(stu[i].teacher == stu[i].stuFirst[j]){
ChoSum += j + 1;
break;
}
}
}
else stuLeft++;
}
printf(" %6.2lf%% %.2lf\n", ((stuSum - stuLeft) * 100.0) / (stuSum * 1.0), (ChoSum * 1.0 )/ ((stuSum - stuLeft) * 1.0));
}
-
main函数
int main(){
freopen("AnaRes.out","w",stdout);
int stuSum;
int teaSum;
int dataNum;
scanf("%d%d%d", &stuSum, &teaSum, &dataNum);
printf("学生数: %d\n导师数: %d\n数据组数: %d\n", stuSum, teaSum, dataNum);
printf(" /*效率分析结果*/\n");
printf(" 匹配效率 平均志愿水平\n");
Student *stu = new Student[stuSum];
Teacher *tea = new Teacher[teaSum];
for(int cas = 1; cas <= dataNum; cas++){
printf("第%03d组数据: ", cas);
StuInit(teaSum, stu, stuSum, cas);
TeaInit(tea, teaSum, stuSum, cas);
qsort(stu, stuSum, sizeof(stu[0]), cmpgpa); //学生按绩点排序
AddStu(tea, stu, stuSum);
CountTeaHot(tea, teaSum);
AddTeaHot(tea, stu, stuSum);
StuAdv(tea, teaSum, stu, stuSum);
Optimize(tea, stu, stuSum);
PutOut(tea, teaSum, stu, stuSum);
}
return 0;
}
分析数据和结果:
-
1.学生数:100,导师数:30,数据组数:35
-
2.学生数:1000,导师数:300,数据组数:35
-
3.学生数:8000,导师数:1100,数据组数:35
-
4.学生数:10000,导师数:2000,数据组数:35
由上面的测试分析,可以大致得出两个结论:
匹配程序的匹配效率初步估计在99%以上,大部分非极限数据的匹配效率都达到了100%,即所有学生均可匹配到导师。
学生的平均志愿水平在2左右。
可以看出我们的匹配程序的匹配效率较高,平均志愿水平也反映出大部分学生能分配到比较满意的导师。
六、后续优化
递归优化(考虑提高平均志愿水平)
前面提到我们的优化方法是按绩点顺序遍历学生,每名学生按其志愿顺序查看导师的名额是否还有剩余,有则调整其志愿至最高志愿,同时,若有调整,原先所选导师必然空出一个名额,此时若该导师未中选学生列表中存在未分配学生,将其与该导师匹配,检测能否成功,以减少未匹配人数。
假使某名学生有上述可优化调整过程,调整后原先所选的导师会空出一个名额,首先检测上述过程,若检测完毕时不存在该导师的未分配学生,则可以检测选择了该导师但已经匹配了其他导师的学生中,是否存在某一学生的志愿列表中该导师的志愿顺序高于已匹配的其他导师,若存在,则可以应用上述的调整过程进行优化,而调整过程又会空出新名额,如此递归下去,直至不存在学生满足条件为止。
该递归过程能最大化学生的平均志愿水平,匹配结果将使更多的学生中选满意的导师。
导师分配调整(考虑导师的学生分配不均问题,但与上述递归优化冲突)
分配过程中有可能出现某些导师没有被分配到任何学生的情况。由于A导师在某些学生的选择志愿中排名较后,学生都匹配高志愿,但学生的高志愿导师的学生数(学生上限范围内)却很多,导师的负担加大,由此,可优化导师的分配:若存在所带学生数量较多的B导师(系统初期已匹配成功)的学生中有志愿是该尚未有带学生的A导师,则将该学生从B导师名下转到A导师。
虽然可以调整导师分配,解决导师的学生分配不均问题,但牺牲了部分学生的高志愿。
七、结对感受及git使用心得
吴宇轩:这次的编程最难的是算法的确定,如何建立一个好的算法,在尽量少的志愿轮数中使更多学生与较高志愿导师匹配成功。而在算法的提出讨论优化中,也证明了“一人计短,两人计长”的人生哲理,算法的优化往往来源于两个人思维火花的碰撞,在讨论中灵光一闪而被提出。 git的使用方便了结对程序的分享,直接在网上交互式代码传送,对每个版本改动过后也能有更直观的印象体会。但在这次的项目中,因为算法之后程序实践上所分项目块比较小,真正用上的类似接口调用这样的方式不太多,不过初步的体会也为后面组队编程打下基础,在多人团队协作中git的优势才能得到更好的体现发挥。
肖承志:从最开始的无从下手,到最后算法程序一步一步地实现,过程是艰难的,但完成后却有一种说不出的奇妙的感觉(exciting~)。最开始讨论算法的时候,我们有各种各样的想法:绩点优先、志愿排序、热度和、平均热度 etc...讨论的深入也让我们对这次的需求问题有了更全面的了解,也划分了接下去实现过程中的各个阶段。两个人结对编程的优势也体现了出来,常常写着写着出了bug自己不知道,队友却能立马看到。在初步完成了程序主体的时候正准备看看结果,却出了一个我们都找不到的bug,调试了一个晚上加上手算数据与程序比对大半个小时才解决了问题,解决后的感觉真是无法言说的好。关于git,一开始我是拒绝的,但上手了之后真是出奇的方便啊提交修改内容的同时,还能与之前的比较,队友写的东西直接从远程仓库下载下来,修改后再提交上去,彻底告别了之前用QQ互传文件的史前时代