首页 > 技术文章 > 学生积分派位算法研究

wala-wo 2014-08-12 10:05 原文

  • 场景描述

              假设条件
             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为设定学生本次可填报的志愿个数,返回结果为高分落选占本次所有学校招生总数的比率*/

 

  • 总结

              由于本算法的中的测试样本是按照随机方式生成的,因此与实际填报情况是有差别的,但是根据人们的行为心理学分析,家长们都喜欢往好的报,比较理性的都会选择调剂保证能够上公办学校,因此实际情况中,学生高分的只要志愿填报合理并且接受调剂,就不会落选。

 

推荐阅读