学生积分派位算法研究
- 场景描述
假设条件
1、schoolCount = 20学校志愿
2、placeCount = 2000个学位
3、personCount = 4000个学生报名
4、按照积分从高到低录取
5、接受调剂的则由系统调剂,不接受调剂的落选
6、选中误差<= 20%,即 2000 * 20% = 400 属于personCount - placeCount
7、设定可报志愿为N个
问题:当N为多少时,分数从高到低的前placeCount个学生中落选比例小于20%,并处于基本稳定状态
- 建模分析
志愿命中概率因素有5个
1、可填报的志愿数
2、是否接受调剂
3、学生的分数
4、填报志愿的顺序
5、各个学校的招收人数
1)接受调剂
1、当前N=placeCount都接受调剂时, 都可以上
2、当前N=placeCount部分不接受调剂时,则可能存在高分落选的学生
2)
1、当N = placeCount, 则
a)当前placeCount学生都全部填写20个志愿时,则积分前placeCount名的学生都可以上
b)当前2000名的学生中部分人填写志愿错误或未全部填写,则可能存在部分人落选,后面的2000名学生存在很小的机会
2、当N < 20,则
a)当前2000名学生的每个人都接受调剂时,则积分前2000名的学生都可以上,后面2000名学生没有任何概率机会
b)当前2000名学生的部分不调剂,则积分前2000名的学生则可能部分不能上
分析结果:
分数高的学生落选跟志愿数N没有决定关系,跟填报的学校顺序有关,跟是否接受调剂有关,若接受调剂,按照分数的高低,高分的学生必然优先上
志愿数N跟学生对多个学校组合优先有关,同时只有在不选择调剂的情况下,N的值越大(即学生可填写的志愿越多)分数高的学生越能保证上线,即当N=schoolCount时相当于接受调剂,但调剂的学校顺序未必与学生的设定的学校顺序一致
- 数据建表
/*学生表*/ CREATE TABLE [bm_person]( [personId] [int] NOT NULL, [personNo] [varchar](50) NULL, /*学生号*/ [personName] [varchar](50) NULL,/*学生名称*/ [point] [int] NULL,/*学生分数*/ [schoolId] [int] NULL,/*被分配的学校*/ [isAdjust] [int] NULL,/*是否调剂*/ [isLose] [int] NULL/*是否高分落选*/ ) go /*产生随机数表,用于初始化使用*/ CREATE TABLE [bm_random]( [id] [int] NOT NULL, [random] [int] NULL, [isUsed] [int] NULL ) go /*报考学校*/ CREATE TABLE [bm_school]( [schoolId] [int] NOT NULL, [schoolName] [varchar](200) NULL, /*学校名称*/ [placeCount] [int] NULL,/*学校招收人数*/ [residue] [int] NULL/*学校剩余招收人数*/ ) go /*学生填报志愿表*/ CREATE TABLE [bm_wish]( [wishId] [int] NOT NULL, [personId] [int] NULL, /*学生id*/ [schoolId] [int] NULL,/*学生填报学校*/ [priority] [int] NULL/*学校志愿优先顺序*/ ) GO
- 数据初始化
/*-------------------------------学校和学生数据初始化-------------------------------------------------------*/ 由于学校和学生的数据比较多,请自行随机生成 本人的学校测试数据为20个学校,学生测试数据为4000 ---------------------------------随机初始化学校招生人数数据-------------------------------------------- DECLARE @COUNT INT = 0; DECLARE @loopId INT = 1; DECLARE @schoolId INT = 0; SELECT @COUNT = COUNT(1) FROM bm_school; SELECT ID = IDENTITY(INT, 1,1),CONVERT(VARCHAR(50),schoolId) schoolId INTO #aa FROM bm_school while (@loopId <= @COUNT) BEGIN SELECT @schoolId = schoolId FROM #aa WHERE id = @loopId UPDATE bm_school SET placeCount = rand() * 250 WHERE schoolId = @schoolId SET @loopId += 1; END DROP table #aa -------------------------------------随机分配积分--------------------------------------------------------- DECLARE @COUNT INT = 0; DECLARE @loopId INT = 1; DECLARE @personId INT = 0; DECLARE @random INT = 0; DECLARE @id INT = 0; SELECT @COUNT = COUNT(1) FROM bm_person while(@loopId <= @COUNT) BEGIN SELECT @personId = personId FROM bm_person WHERE personId = @loopId SELECT @id = id, @random = random FROM bm_random WHERE isUsed = 0 ORDER BY NEWID() UPDATE bm_random SET isUsed = 1 WHERE id = @id UPDATE bm_person SET point = @random WHERE personId = @personId SET @loopId +=1; END -------------------------------------初始化学生志愿和志愿填报顺序-------------------------------------------------- DECLARE @personCount Int = 0 ; DECLARE @personLoopId INt = 1; DECLARE @personid INT = 0; CREATE TABLE #priority(Id int IDENTITY(1,1), schoolId INT) TRUNCATE TABLE bm_wish; SELECT @personCount = count(1) FROM bm_person WHILE(@personLoopId <= @personCount) BEGIN Select @personid = personId FROm bm_person WHERE personid = @personLoopId INSERT INTO bm_wish(personId,schoolId) SELECT @personid, schoolId FROM bm_school; INSERT INTO #priority SELECT convert(VARCHAR(20),schoolId) schoolId FROM bm_school ORDER BY NEWID() /*随机更新学生志愿的优先顺序*/ UPDATE bm_wish SET priority = pr.id FROM #priority pr, bm_wish bw WHERE personId = @personId And pr.schoolId = bw.schoolId truncate TABLE #priority SET @personLoopId += 1; END DROP TABLE #priority; -----------------------------------------更新学生是否调剂志愿-------------------------------------------- DECLARE @personCount Int = 0 ; DECLARE @personLoopId INt = 1; SELECT @personCount = count(1) FROM bm_person print @personCount; WHILE(@personLoopId <= @personCount) BEGIN print @personLoopId; update bm_person set isAdjust = CASE WHEN rand() > 0.5 THEN 1 ELSE 0 END where personid = @personLoopId SET @personLoopId += 1; END
- 算法实现
/* 支持分数从高到低分配 支持按照学生志愿的优先顺序分配 支持学生的调剂分配 */ ALTER PROCEDURE dbo.[Pro_StudentAssign] @wishCount INT AS BEGIN; BEGIN TRY; BEGIN TRAN; /*学生数*/ DECLARE @personCount INT = 0; /*学生循环因子*/ DECLARE @personLoopId INT = 1; /*学生志愿循环因子*/ DECLARE @wishLoopId INT = 1; /*学校id*/ DECLARE @schoolId INT = 0; /*学生id*/ DECLARE @personId INT = 0; /*是否可以按志愿分配*/ DECLARE @isCanWish INT = 0; /*是否调剂*/ DECLARE @isAdjust INT = 0; /*可分配的学校学位总数*/ DECLARE @sumPlaceCount INT = 0; SELECT @sumPlaceCount = SUM(placeCount) FROM bm_school /*重置学校学位数*/ UPDATE bm_school SET residue = placeCount /*重置学生志愿分配结果*/ UPDATE bm_person SET schoolId = 0, isLose = 0; /*按照积分从高到低读取学生数据*/ SELECT CONVERT(VARCHAR,personId) personId,id=IDENTITY(INT,1,1), point,isAdjust INTO #person FROM bm_person ORDER BY point DESC /*读取学生数*/ SELECT @personCount = COUNT(1) FROM #person /*遍历学生*/ WHILE(@personLoopId <= @personCount) BEGIN SET @wishLoopId = 1; SET @schoolId = 0 SET @isCanWish = 0 SELECT @personId = personId, @isAdjust = isAdjust FROM #person WHERE Id = @personLoopId; /*判断该学生是否可以按志愿分配学校学位*/ /* 是否按照志愿来分配受两个因素影响 1、填报的志愿个数 2、填报志愿的招收人数 */ With TopCountWishCTE AS( /*读取该学生的最优先的@wishCount个志愿*/ SELECT TOP(@wishCount) * FROM bm_wish WHERE personId = @personId ORDER BY priority ASC ) SELECT @isCanWish = COUNT(1) FROM bm_school bs WHERE residue > 0 AND EXISTS( SELECT 1 From TopCountWishCTE bw Where bs.schoolId = bw.schoolId ); print ('@isAdjust='+ convert(varchar, @isAdjust)+'@wishCount='+ CONVERT(varchar,@wishCount) +',@personId='+ convert(varchar, @personId) +',@isCanWish='+ convert(varchar,@isCanWish)); /*志愿内的学校学位未满,可以分配到学位*/ IF @isCanWish > 0 BEGIN /*逐个检索填写优先志愿学校,若第一志愿已满,则检索第二个志愿,逐个检索*/ SELECT Top 1 @schoolId = bw.schoolId FROM bm_wish bw, bm_school bs WHERE personId = @personId AND bw.schoolId = bs.schoolId AND bs.residue > 0 ORDER BY priority ASC print '@schoolId = '+ convert(varchar,@schoolId); END ELSE BEGIN /*填报志愿学校学位已满并且接受调剂*/ IF @isAdjust = 1 BEGIN /*随机读取一个有剩余学位的学校*/ SELECT @schoolId = schoolId FROM bm_school WHERE residue > 0 ORDER BY NEWID() END ELSE BEGIN /*记录落选的学生*/ UPDATE bm_person SET isLose = 1 WHERE personId = @personId --PRINT '若未接受调剂有可能高分落榜,只能去读民办小学' /*若未接受调剂有可能高分落榜,只能去读民办小学*/ END END /*若学生成功分配上则对学生标记学校学位,以及更新学位数*/ IF @schoolId > 0 And @schoolId IS NOT NULL BEGIN /*更新剩余的学位数*/ UPDATE bm_school SET residue = residue - 1 WHERE schoolId = @schoolId /*标记学生已经被分配上*/ UPDATE bm_person SET schoolId = @schoolId WHERE personId = @personId END SET @personLoopId += 1; END /*前@sumPlaceCount落选的高分学生占比*/ IF @personCount > 0 BEGIN SELECT COUNT(1) failCount,convert(DECIMAL(4,2), COUNT(1) * 100.00 / (@sumPlaceCount * 1.00))failRate From( SELECT TOP(@sumPlaceCount) * FROM bm_person ORDER BY Point DESC )as TableLose WHERE isLose = 1; END PRINT '@sumPlaceCount='+CONVERT(VARCHAR,@sumPlaceCount); DROP TABLE #person; COMMIT TRAN ; END TRY BEGIN CATCH ROLLBACK TRAN ; END CATCH END;
- 结果测试
exec dbo.[Pro_StudentAssign] N;/*N为设定学生本次可填报的志愿个数,返回结果为高分落选占本次所有学校招生总数的比率*/
- 总结
由于本算法的中的测试样本是按照随机方式生成的,因此与实际填报情况是有差别的,但是根据人们的行为心理学分析,家长们都喜欢往好的报,比较理性的都会选择调剂保证能够上公办学校,因此实际情况中,学生高分的只要志愿填报合理并且接受调剂,就不会落选。