本文转载之http://artwl.cnblogs.com

 

5、 变异算子

在变异过程中主要是要保证替换题目至少包含一个被替换题的有效知识点(期望试卷中也包含此知识点),并要类型相同,分数相同而题号不同。 算法实现代码如下:

复制代码
/// <summary>
/// 变异算子
/// </summary>
/// <param name="unitList"> 种群 </param>
/// <param name="problemList"> 题库 </param>
/// <param name="paper"> 期望试卷 </param>
/// <returns> List </returns>
public List < Unit > Change(List < Unit > unitList, List < Problem > problemList, Paper paper)
{
Random rand
= new Random();
int index = 0 ;
unitList.ForEach(
delegate (Unit u)
{
// 随机选择一道题
index = rand.Next( 0 , u.ProblemList.Count);
Problem temp
= u.ProblemList[index];

// 得到这道题的知识点
Problem problem = new Problem();
for ( int i = 0 ; i < temp.Points.Count; i ++ )
{
if (paper.Points.Contains(temp.Points[i]))
{
problem.Points.Add(temp.Points[i]);
}
}

// 从数据库中选择包含此题有效知识点的同类型同分数不同题号试题
var otherDB = from a in problemList
where a.Points.Intersect(problem.Points).Count() > 0
select a;

List
< Problem > smallDB = otherDB.Where(p => IsContain(paper, p)).Where(o => o.Score == temp.Score && o.Type == temp.Type && o.ID != temp.ID).ToList();

// 从符合要求的试题中随机选一题替换
if (smallDB.Count > 0 )
{
int changeIndex = rand.Next( 0 , smallDB.Count);
u.ProblemList[index]
= smallDB[changeIndex];
}
});

// 计算知识点覆盖率跟适应度
unitList = GetKPCoverage(unitList, paper);
unitList
= GetAdaptationDegree(unitList, paper, kpcoverage, difficulty);
return unitList;

}

复制代码

6、调用示例

遗传算法主要算法上面都已实现,现在就是调用了。调用过程按如下流程图进行:

基于遗传算法的自动组卷系统流程图

这里初始种群大小设定为20,最大迭代次数为500,适应度为0.98,选择算子选择次数为10次,交叉算子产生的个体数量为20,期望试卷难度系数为0.72,总分为100分,各种题型题数为:20(单选), 5(多选), 10(判断), 7(填空), 5(问答),包含的知识点为:1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81。代码如下:

复制代码
/// <summary>
/// 调用示例
/// </summary>
public void Show()
{
// 题库
DB db = new DB();

// 期望试卷
Paper paper = new Paper()
{
ID
= 1 ,
TotalScore
= 100 ,
Difficulty
= 0.72 ,
Points
= new List < int > () { 1 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 , 21 , 23 , 25 , 27 , 29 , 31 , 33 , 35 , 37 , 39 , 41 , 43 , 45 , 47 , 49 , 51 , 53 , 55 , 57 , 59 , 61 , 63 , 65 , 67 , 69 , 71 , 73 , 75 , 77 , 79 , 81 },
EachTypeCount
= new [] { 20 , 5 , 10 , 7 , 5 }
};

// 迭代次数计数器
int count = 1 ;

// 适应度期望值
double expand = 0.98 ;

// 最大迭代次数
int runCount = 500 ;

// 初始化种群
List < Unit > unitList = CSZQ( 20 , paper, db.ProblemDB);
Console.WriteLine(
" \n\n -------遗传算法组卷系统(http://www.cnblogs.com/durongjian/)---------\n\n " );
Console.WriteLine(
" 初始种群: " );
ShowUnit(unitList);
Console.WriteLine(
" -----------------------迭×××始------------------------ " );

// 开始迭代
while ( ! IsEnd(unitList, expand))
{
Console.WriteLine(
" 在第 " + (count ++ ) + " 代未得到结果 " );
if (count > runCount)
{
Console.WriteLine(
" 计算 " + runCount + " 代仍没有结果,请重新设计条件! " );
break ;
}

// 选择
unitList = Select(unitList, 10 );

// 交叉
unitList = Cross(unitList, 20 , paper);

// 是否可以结束(有符合要求试卷即可结束)
if (IsEnd(unitList, expand))
{
break ;
}

// 变异
unitList = Change(unitList, db.ProblemDB, paper);
}
if (count <= runCount)
{
Console.WriteLine(
" 在第 " + count + " 代得到结果,结果为:\n " );
Console.WriteLine(
" 期望试卷难度: " + paper.Difficulty + " \n " );
ShowResult(unitList, expand);
}

}

复制代码

最后在控制台中调用此方法即可。

7、其他辅助方法

在上面的代码中还调用了几个辅助方法,下面一并给出:

复制代码
#region 是否达到目标
/// <summary>
/// 是否达到目标
/// </summary>
/// <param name="unitList"> 种群 </param>
/// <param name="endcondition"> 结束条件(适应度要求) </param>
/// <returns> bool </returns>
public bool IsEnd(List < Unit > unitList, double endcondition)
{
if (unitList.Count > 0 )
{
for ( int i = 0 ; i < unitList.Count; i ++ )
{
if (unitList[i].AdaptationDegree >= endcondition)
{
return true ;
}
}
}
return false ;
}
#endregion

#region 显示结果
/// <summary>
/// 显示结果
/// </summary>
/// <param name="unitList"> 种群 </param>
/// <param name="expand"> 期望适应度 </param>
public void ShowResult(List < Unit > unitList, double expand)
{
unitList.OrderBy(o
=> o.ID).ToList().ForEach( delegate (Unit u)
{
if (u.AdaptationDegree >= expand)
{
Console.WriteLine(
" " + u.ID + " 套: " );
Console.WriteLine(
" 题目数量\t知识点分布\t难度系数\t适应度 " );
Console.WriteLine(u.ProblemCount
+ " \t\t " + u.KPCoverage.ToString( " f2 " ) + " \t\t " + u.Difficulty.ToString( " f2 " ) + " \t\t " + u.AdaptationDegree.ToString( " f2 " ) + " \n\n " );
}
});
}
#endregion

#region 显示种群个体题目编号
/// <summary>
/// 显示种群个体题目编号
/// </summary>
/// <param name="u"> 种群个体 </param>
public void ShowUnit(Unit u)
{
Console.WriteLine(
" 编号\t知识点分布\t难度系数 " );
Console.WriteLine(u.ID
+ " \t " + u.KPCoverage.ToString( " f2 " ) + " \t\t " + u.Difficulty.ToString( " f2 " ));
u.ProblemList.ForEach(
delegate (Problem p)
{
Console.Write(p.ID
+ " \t " );
});
Console.WriteLine();
}

#endregion

复制代码

经园友提醒,少了一个方法,这里补上:

复制代码
#region 题目知识点是否符合试卷要求
/// <summary>
/// 题目知识点是否符合试卷要求
/// </summary>
/// <param name="paper"> 期望试卷 </param>
/// <param name="problem"> 一首试题 </param>
/// <returns> bool </returns>
private bool IsContain(Paper paper, Problem problem)
{
for ( int i = 0 ; i < problem.Points.Count; i ++ )
{
if (paper.Points.Contains(problem.Points[i]))
{
return true ;
}
}
return false ;
}

#endregion

复制代码

后记

大功告成啦,运行效果在文章开头已经给出。 当然,由于题库每次运行都不同,因此每次运行的结果也都不同。文中适应度函数用的是线性的,也可以根据运行情况进行适当调整,优化,欢迎大家提出不同意见。