- 场景描述
假设条件
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为设定学生本次可填报的志愿个数,返回结果为高分落选占本次所有学校招生总数的比率*/
- 总结
由于本算法的中的测试样本是按照随机方式生成的,因此与实际填报情况是有差别的,但是根据人们的行为心理学分析,家长们都喜欢往好的报,比较理性的都会选择调剂保证能够上公办学校,因此实际情况中,学生高分的只要志愿填报合理并且接受调剂,就不会落选。