1.结队队员
031502443 郑书豪
031502404 陈邡
2.GitHub链接
3.问题重述
编码实现一个部门与学生的智能匹配的程序。部门有数据:部门标签,部门人数限制,部门时间段,部门编号;学生有数据:学生编号,学生兴趣标签,学生空闲时间段,部门意愿。这些数据由编程实现自动生成,在进行智能匹配的程序,将学生合理的分到各个部门,使无学生的部门较少,无部门的学生较少,为较合理的匹配。
4.数据建模&生成数据
- 学生类
class Students
{
private:
string free_t[20];//学生空闲时间
string stu_no;//学生号
string app_dep[5];//学生志愿部门
string s_tags[10];//学生标签
public:
int s_time[7][40];//空闲时间数组
void set_free_t(string ft[]);//空闲时间赋值
void set_stu_no(string sn);//学生号赋值
void set_app_dep(string ad[]);//志愿部门赋值
void set_s_tags(string st[]);//标签赋值
void get_time();//空闲时间计算
string get_stu_no();//获得学生号
string get_app_dep(int n);//获得志愿部门
string *get_s_tags();//获得标签
};
- 部门类
class Departments
{
private:
string e_sche[10];//部门活动时间
int mem_lmt;//部门纳新人数
string dep_no;//部门号
string d_tags[10];//部门标签
public:
struct acc_stu
{
acc_stu() { s_no = 0; s_sum = 0; }
int s_no;
int s_sum;
};//纳新成员结构体
acc_stu a_s[15];//纳新成员结构体数组
int d_time[7][20];//活动时间数组
void set_e_sche(string es[]);//活动时间赋值
void set_mem_lmt(int ml);//纳新人数赋值
void set_dep_no(string dn);//部门号赋值
void set_d_tags(string dt[]);//标签赋值
void get_time();//计算活动时间
int get_mem_lmt();//获得部门纳新人数
string get_dep_no();//获得部门编号
string *get_d_tags();//获得部门标签
};
生成数据时,已生成部门标签为例,先事先构建一个放标签的数组,在进行随机数的生成去随机选取标签(随机数生成时进行随机数的去重),由程序设定例如部门随机生成2~5个标签,可由 int tags_num = rand() % 4 + 2 计算而得;以此类推......
- 学生数据的生成:
for (int i = 0; i < stu_num; i++)
{
Json::Value Stu;
for each(string s in stu[i].free_t)
{
if (s == "") break;
Stu["free_time"].append(s);
}
Stu["student_no"] = Json::Value(stu[i].stu_no);
for each(string s in stu[i].app_dep)
{
if (s == "") break;
Stu["applications_department"].append(s);
}
for each(string s in stu[i].s_tags)
{
if (s == "") break;
Stu["tags"].append(s);
}
root["students"].append(Stu);
}
- 部门数据的生成:
for (int i = 0; i < dep_num; i++)
{
Json::Value Dep;
for each(string s in dep[i].e_sche)
{
if (s == "") break;
Dep["event_schedules"].append(s);
}
Dep["member_limit"] = Json::Value(dep[i].mem_lmt);
Dep["department_no"] = Json::Value(dep[i].dep_no);
for each(string s in dep[i].d_tags)
{
if (s == "") break;
Dep["tags"].append(s);
}
root["departments"].append(Dep);
}
5.核心算法
算法中采用了由学生意愿分配部门的形式。
算法步骤描述:
先将所有学生移入未分配部门的队列,每个部门建立一个接受学生的优先队列(意愿优先级>兴趣优先级=时间优先级)。
- 第一轮,先将所有未分配部门的队列中有第一意愿部门的学生找出,每个有第一意愿部门的学生针对第一意愿部门进行分配,会出现以下情况:1. 部门人数还没有达到上限,学生的活动时间至少有一个时间段符合则接受该学生,该学生移出未分配部门的队列,按兴趣匹配的个数和活动时间匹配的个数两者权重相同进行优先级计算,加入该部门的优先级队列;2. 部门人数达到上限,则待分配的学生插入优先队列,该学生移出未分配部门的队列,再将优先队列的最后一个学生移出优先队列,该学生移入未分配队列。
- 第二轮,先将所有未分配部门的队列中有第二意愿部门的学生找出,每个有第二意愿部门的学生针对第二意愿部门进行分配,会出现以下情况:1. 部门人数还没有达到上限,学生的活动时间至少有一个时间段符合则接受该学生,该学生移出未分配部门的队列,按兴趣匹配的个数和活动时间匹配的个数两者权重相同进行优先级计算(意愿的优先级最高,固第二志愿学生优先级一定低于第一志愿学生,以此类推),加入该部门的优先级队列;2. 部门人数达到上限,则待分配的学生插入优先队列,该学生移出未分配部门的队列,再将优先队列的最后一个学生移出优先队列,该学生移入未分配队列。
- 第三轮……
- 第四轮……
- 第五轮……以此类推
- 第六轮,此时未分配部门的队列中剩下的学生有:没有意愿部门的学生,意愿部门已满不接收的学生。此时所有未满的部门计算优先级(兴趣优先级设为时间优先级的两倍),对未分配部门的学生进行分配(兴趣标签至少要有一个相同,时间段至少有一个符合),最后剩下学生为无法分配学生。
怎么证明这个算法肯定能够得到稳定的学生部门关系:
- (1) 前五轮的时候,根据学生自己的意愿依次进行对学生进行部门的分配,学生每一轮的时候都根据给定的优先级进行学生的筛选,此时已分配的学生部门关系是稳定的。
- (2) 第六轮的时候,未分配部门的队列中剩下的学生有:没有意愿部门的学生,意愿部门已满不接收的学生。因此不在考虑学生的意愿,而按照学生的兴趣进行划分,只要学生时间段至少有一个符合,兴趣标签至少要有一个相同,且该部门有空缺,就会被分配。最后剩下的学生意愿部门已满拒收,兴趣部门时间不符,无法分配,此时的分配关系最为稳定。
算法流程图:
- 前五轮匹配:
//前五轮分配
//志愿优先原则 学生优先级按照志愿位置、时间匹配次数、标签匹配次数进行权重计算 权重分别为 10、5、2
for (int q = 0; q < 5; q++)
{
for (int i = 0; i < stu_num; i++)
{
if (Stu[i].get_app_dep(q + 1) != "")
//学生第q个志愿为空则不进行操作
{
for (int j = 0; j < dep_num; j++)
//将志愿与部门匹配
{
if (Stu[i].get_app_dep(q + 1) == Dep[j].get_dep_no())
//若匹配
{
if (noInArray(Dep[j].a_s, i))
//若学生已进入该部门纳新成员数组则不做考虑
{
continue;
}
if (acc[j] < Dep[j].get_mem_lmt() && cmp_time(Stu[i], Dep[j]) >= 1)
//志愿部门人数未满且时间至少匹配一次
{
acc[j]++;//部门纳新人数加一
sum = (5 - q) * 10 + cmp_time(Stu[i], Dep[j]) * 5 + cmp_tags(Stu[i], Dep[j]) * 2;//计算学生优先级
insert(Dep[j].a_s, sum, i, Dep[j].get_mem_lmt());//向部门纳新数组插入学生
assign[i]++;//纳入学生分配情况更新
}
else if (acc[j] == Dep[j].get_mem_lmt() && cmp_time(Stu[i], Dep[j]) >= 1)
//志愿部门人数已满且时间至少匹配一次
{
sum = (5 - q) * 10 + cmp_time(Stu[i], Dep[j]) * 5 + cmp_tags(Stu[i], Dep[j]) * 2;//计算学生优先级
if (sum > Dep[j].a_s[Dep[j].get_mem_lmt() - 1].s_sum)
//若学生优先级大于部门纳新数组中优先级最低成员则将其取代
{
assign[Dep[j].a_s[Dep[j].get_mem_lmt() - 1].s_no]--;//淘汰学生分配情况更新
insert(Dep[j].a_s, sum, i, Dep[j].get_mem_lmt());//向部门纳新数组插入学生
assign[i]++;//纳入学生分配情况更新
}
}
}
}
}
}
}
- 第六轮匹配:
//第六轮分配
for (int k = 0; k < dep_num; k++)
{
if (acc[k] < Dep[k].get_mem_lmt())
//找出未满的部门
{
for (int w = 0; w < stu_num; w++)
{
if (assign[w] == 0)
//若学生未进入任何部门
{
if (acc[k] < Dep[k].get_mem_lmt() && cmp_time(Stu[w], Dep[k]) >= 1 && cmp_tags(Stu[w], Dep[k]) >= 1)
//志愿部门人数未满且时间至少匹配一次且标签至少匹配一次
{
acc[k]++;//部门纳新人数加一
sum = cmp_time(Stu[w], Dep[k]) + 2 * cmp_tags(Stu[w], Dep[k]);//计算学生优先级
insert(Dep[k].a_s, sum, w, Dep[k].get_mem_lmt());//向部门纳新数组插入学生
assign[w]++;//纳入学生分配情况更新
}
else if (acc[k] == Dep[k].get_mem_lmt() && cmp_time(Stu[w], Dep[k]) >= 1 && cmp_tags(Stu[w], Dep[k]) >= 1)
//志愿部门人数已满且时间至少匹配一次且标签至少匹配一次
{
sum = cmp_time(Stu[w], Dep[k]) + 2 * cmp_tags(Stu[w], Dep[k]);//计算学生优先级
if (sum > Dep[k].a_s[Dep[k].get_mem_lmt() - 1].s_sum)
{
assign[Dep[k].a_s[Dep[k].get_mem_lmt() - 1].s_no]--;//淘汰学生分配情况更新
insert(Dep[k].a_s, sum, w, Dep[k].get_mem_lmt());//向部门纳新数组插入学生
assign[w]++;//纳入学生分配情况更新
}
}
}
}
}
}
6.结果分析
次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 平均 |
学生数 | 300 | 300 | 300 | 300 | 300 | 300 | 300 | 300 | 300 | 300 | 300 |
部门数 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 |
未匹配学生 | 22 | 15 | 18 | 14 | 23 | 9 | 15 | 27 | 20 | 20 | 18 |
未匹配部门 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7.代码规范
- 命名:类名用的是第一个字母大写,函数名采用驼峰命名法,局部变量根据需要采用平时常用的命名,对于命名,基本的要求是见名知意。
- 运算符间隔:一元运算符不做间隔处理,二元运算符前后各加一个空格。
- 注释:简单的程序没注释,复杂的程序注释放在相应语句的上方。相应变量的注释放到右边。
- 代码示例
/*
活动/空闲时间匹配函数
输入:单个学生类对象,单个部门类对象
返回:学生和部门对象时间匹配次数
*/
int cmp_time(Students s, Departments d)
{
int cnt = 0;//次数统计
for (int i = 0; i < 7; i++)
{
for (int j = 0; j < 20; j++)
{
if (d.d_time[i][j] == 0)
//若当前时间点为0则继续读取(一个时间段在时间数组中占2个位置,向后两位进行下一时间段读取)
{
j++;
continue;
}
for (int k = 0; k < 40; k++)
{
if (s.s_time[i][k] == 0)
//若当前时间点为0则继续读取(一个时间段在时间数组中占2个位置,向后两位进行下一时间段读取)
{
k++;
continue;
}
if (d.d_time[i][j] >= s.s_time[i][k] && d.d_time[i][j + 1] <= s.s_time[i][k + 1])
//若时间段匹配则计数加一
cnt++;
//计数后继续读取(一个时间段在时间数组中占2个位置,向后两位进行下一时间段读取)
k++;
}
}
}
return cnt;
}
8.程序评价
本学生部门智能分配程序的分配结果基本满足了题意要求,但是程序尚且存在着一些不足。在随机生成的学生信息和部门信息数据和现实生活中有所偏颇。学生的空闲时间大多集中在周末,而部门一般也会根据学生的时间来设置常规活动时间。兴趣标签,现实中往往几种标签的人数比较多,而一些冷门兴趣人数较少,不应该是随机的分配。该程序通过六轮的匹配,将没有意愿部门,没有时间段参加部门,没有兴趣与部门相同的人筛选了出来。
9.结队感受
结队来说,好庆幸抱着了大腿,两个人的合作中也学到了很多东西。比如,变成习惯非常不良好的我,开始的时候一般都不会去几个源文件,而是函数,类,全局变量,main()函数......全部扔到一个源文件中间去,以及平常写函数时的种种不良习惯,是的两个人的对接不太流畅,导致后期接过队友写好json的部分,读代码时间就占用了好久,以及写main()函数的时候,错误百出,磕磕绊绊,全靠队友带飞。事实上,昨晚已经刚到了四五点。软工实践,这种有个任务在后面追着你跑的课,的确能够学到好多东西,学到了json这个东西其实也没有那么神秘,只是几个reader.h,writer.h,value.h的几个库函数的使用。