问题描述:
- Suppose there are n facilities and m customers. We wish
to choose:- which of the n facilities to open
- the assignment of customers to facilities
- The objective is to minimize the sum of the opening cost
and the assignment cost. - The total demand assigned to a facility must not exceed
its capacity.
report
在本次实践中,我分别采用了蚁群算法和遗传算法来对问题进行求解。
蚁群算法是一种用来寻找优化路径的概率型算法。它由Marco Dorigo于1992年在他的博士论文中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。
遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法
代码
// 遗传算法
/** 染色体数量 */
let chromosomeNum = 100
/** 适应度矩阵(下标:染色体编号、值:该染色体的适应度) */
let adaptability = []
/** 自然选择的概率矩阵(下标:染色体编号、值:该染色体被选择的概率) */
let selectionProbability = []
/** 染色体复制的比例(每代中保留适应度较高的染色体直接成为下一代) */
let cp = 0.2
/** 参与交叉变异的染色体数量 */
let crossoverMutationNum = chromosomeNum * (1-cp)
/** 任务处理时间结果集([迭代次数][染色体编号]) */
// let resultData = []
/**
* 初始化遗传算法
* 初始化 顾客集合、处理设施集合
* @param _facilities 设施参数
* @param _demands 顾客的要求
* @param _assignmentCost 顾客到指定设施的cost
*/
function startGa(_facilities, _demands, _assignmentCost) {
facilities = _facilities.reduce((all, now) => all.concat(Number(now.split(' ').filter(n=>n)[0])), [])
openCost = _facilities.reduce((all, now) => all.concat(Number(now.split(' ').filter(n=>n)[1])), [])
customers= _demands.reduce((all, now) => all.concat(now.split(' ').filter(n =>n).map(n =>Number(n))), [])
let tp =[]
/* _assignmentCost.forEach(_assign => {
assignment.push(_assign.split(' ').filter(n => n).map(n => Number(n)))
}) */
let nums = _assignmentCost.reduce((all, now) => all.concat(now.split(' ').filter(n =>n).map(n =>Number(n))), [])
while (nums.length) {
console.log(nums)
tp.push(nums.splice(0, customerNum))
}
debugger
for (let i = 0; i < customerNum; i++) {
for (let j = 0; j < facilityNum; j++) {
if (!assignment[i]) {
assignment[i] = []
}
assignment[i][j] = tp[j][i]
}
}
}
// startGa(lines.slice(1, facilityNum+1), lines.slice(facilityNum+1, facilityNum+1+customerNum/10), lines.slice(facilityNum+1+customerNum/10))
/**
* 遗传算法
*/
function ga() {
// 迭代搜索
gaSearch(iteratorNum, chromosomeNum)
}
/**
* 计算 染色体适应度
* @param chromosomeMatrix
*/
function calAdaptability(chromosomeMatrix) {
// 计算每条染色体的cost
adaptability = calCost_oneIt(chromosomeMatrix).map(n => 1/n)
}
/**
* 计算自然选择概率
* @param adaptability
*/
function calSelectionProbability(adaptability) {
selectionProbability = []
// 计算适应度总和
let sumAdaptability = 0
for (let i=0; i<chromosomeNum; i++) {
sumAdaptability += adaptability[i]
}
// 计算每条染色体的选择概率
for (let i=0; i<chromosomeNum; i++) {
selectionProbability.push(adaptability[i] / sumAdaptability)
}
}
/**
* 迭代搜索
* @param iteratorNum 迭代次数
* @param chromosomeNum 染色体数量
*/
function gaSearch(iteratorNum, chromosomeNum) {
// 初始化第一代染色体
let chromosomeMatrix = createGeneration()
// 迭代繁衍
for (let itIndex=1; itIndex<iteratorNum; itIndex++) {
// 计算上一代各条染色体的适应度
calAdaptability(chromosomeMatrix)
// 计算自然选择概率
calSelectionProbability(adaptability)
// 生成新一代染色体
chromosomeMatrix = createGeneration(chromosomeMatrix)
}
}
/**
* 交叉生成{crossoverMutationNum}条染色体
* @param chromosomeMatrix 上一代染色体矩阵
*/
function cross(chromosomeMatrix) {
let newChromosomeMatrix = []
for (let chromosomeIndex=0; chromosomeIndex<crossoverMutationNum; chromosomeIndex++) {
// 采用轮盘赌选择父母染色体
let chromosomeDad = chromosomeMatrix[RWS(selectionProbability)].slice(0)
let chromosomeMom = chromosomeMatrix[RWS(selectionProbability)].slice(0)
// 交叉
let crossIndex = random(0, customerNum-1)
chromosomeDad.splice(crossIndex)
chromosomeDad = chromosomeDad.concat(chromosomeMom.slice(crossIndex))
while (!checkIfLegal(chromosomeDad)) {
console.log('cross')
chromosomeDad = chromosomeMatrix[RWS(selectionProbability)].slice(0)
chromosomeMom = chromosomeMatrix[RWS(selectionProbability)].slice(0)
crossIndex = random(0, customerNum-1)
chromosomeDad.splice(crossIndex)
chromosomeDad = chromosomeDad.concat(chromosomeMom.slice(crossIndex))
}
newChromosomeMatrix.push(chromosomeDad)
}
return newChromosomeMatrix
}
/*
*看一条染色体是否是可行解
*@param chromosome 染色体
*/
function checkIfLegal(chromosome) {
let usedCapacity= {}
for (let i = 0; i < chromosome.length; i++) {
if (!usedCapacity[chromosome[i]]) {
usedCapacity[chromosome[i]] = customers[i]
} else {
usedCapacity[chromosome[i]] = Number(usedCapacity[chromosome[i]]) + Number(customers[i])
}
}
for (let i of Object.keys(usedCapacity)) {
if (usedCapacity[i] > facilities[i]) {
return false
}
}
return true
}
/**
* 从数组中寻找最大的n个元素
* @param array
* @param n
*/
function maxN(array, n) {
// 将一切数组升级成二维数组,二维数组的每一行都有两个元素构成[原一位数组的下标,值]
let matrix = []
for (let i=0; i<array.length; i++) {
matrix.push([i, array[i]])
}
// 对二维数组排序
for (let i=0; i<n; i++) {
for (let j=1; j<matrix.length; j++) {
if (matrix[j-1][1] > matrix[j][1]) {
let temp = matrix[j-1]
matrix[j-1] = matrix[j]
matrix[j] = temp
}
}
}
// 取最大的n个元素
let maxIndexArray = []
for (let i=matrix.length-1; i>matrix.length-n-1; i--) {
maxIndexArray.push(matrix[i][0])
}
return maxIndexArray
}
/**
* 复制(复制上一代中优良的染色体)
* @param chromosomeMatrix 上一代染色体矩阵
* @param newChromosomeMatrix 新一代染色体矩阵
*/
function copy(chromosomeMatrix, newChromosomeMatrix) {
// 寻找适应度最高的N条染色体的下标(N=染色体数量*复制比例)
let chromosomeIndexArr = maxN(adaptability, chromosomeNum*cp)
// 复制
for (let i=0; i<chromosomeIndexArr.length; i++) {
let chromosome = chromosomeMatrix[chromosomeIndexArr[i]]
newChromosomeMatrix.push(chromosome)
}
return newChromosomeMatrix
}
/**
* 计算所有染色体的cost
* @param chromosomeMatrix
*/
function calCost_oneIt(chromosomeMatrix) {
// 计算每条染色体的cost
let costArray_oneIt = []
for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
let cost = 0
let usedFacilities= {}
for (let facilityIndex=0 ;facilityIndex<facilityNum; facilityIndex++) {
for (let customerIndex=0; customerIndex<customerNum; customerIndex++) {
if (chromosomeMatrix[chromosomeIndex][customerIndex] == facilityIndex) {
cost = Number(cost) + Number(assignment[customerIndex][facilityIndex])
// 已经开启的设施不再加openCost
if (!usedFacilities[facilityIndex]) {
usedFacilities[facilityIndex] = 1
cost = Number(cost) + Number(openCost[facilityIndex])
}
}
}
}
costArray_oneIt.push(cost)
}
resultDataGa.push(costArray_oneIt)
return costArray_oneIt
}
/**
* 繁衍新一代染色体
* @param chromosomeMatrix 上一代染色体
*/
function createGeneration(chromosomeMatrix) {
// 第一代染色体,随机生成
if (!chromosomeMatrix) {
let newChromosomeMatrix = []
for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
let chromosomeMatrix_i = []
let usedCapacity = {}
for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
let index = random(0, facilityNum-1)
// 第一次用到这个设施时,加上open cost
if (!usedCapacity[index]) {
usedCapacity[index] = customers[customerIndex]
}
// 不可以超过容量限制
while (usedCapacity[index] + customers[customerIndex] > facilities[index]) {
console.log(398)
console.log(usedCapacity[index], customers[customerIndex], facilities[index])
index = random(0, facilityNum-1)
}
usedCapacity[index] = Number(usedCapacity[index]) + Number(customers[customerIndex])
chromosomeMatrix_i.push(index)
}
newChromosomeMatrix.push(chromosomeMatrix_i)
}
// 计算当前染色体的cost
calCost_oneIt(newChromosomeMatrix)
return newChromosomeMatrix
}
// 交叉生成{crossoverMutationNum}条染色体
let newChromosomeMatrix = cross(chromosomeMatrix)
// 变异
newChromosomeMatrix = mutation(newChromosomeMatrix)
// 复制
newChromosomeMatrix = copy(chromosomeMatrix, newChromosomeMatrix)
// 计算当前染色体的任务处理时间
calCost_oneIt(newChromosomeMatrix)
return newChromosomeMatrix
}
/**
* 轮盘赌算法
* @param selectionProbability 概率数组(下标:元素编号、值:该元素对应的概率)
* @returns {number} 返回概率数组中某一元素的下标
*/
function RWS(selectionProbability) {
let sum = 0
let rand = Math.random()
for (let i=0; i<selectionProbability.length; i++) {
sum += selectionProbability[i]
if (sum >= rand) {
return i
}
}
}
/**
* 变异
* @param newChromosomeMatrix 新一代染色体矩阵
*/
function mutation(newChromosomeMatrix) {
// 随机找一条染色体
let chromosomeIndex = random(0, crossoverMutationNum-1)
// 随机找一个顾客
let customerIndex = random(0, customerNum-1)
// 随机找一个设施
let facilityIndex = random(0, facilityNum-1)
newChromosomeMatrix[chromosomeIndex][customerIndex] = facilityIndex
while (!checkIfLegal(newChromosomeMatrix[chromosomeIndex])) {
console.log(461)
facilityIndex = random(0, facilityNum-1)
newChromosomeMatrix[chromosomeIndex][customerIndex] = facilityIndex
}
return newChromosomeMatrix
}
/**
* 渲染视图
* @param resultData
*/
function drawGa(resultData) {
// 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.getElementById('ga'))
// 指定图表的配置项和数据
let option = {
title: {
text: '基于遗传算法的容量设施问题'
},
tooltip : {
trigger: 'axis',
showDelay : 0,
axisPointer:{
show: true,
type : 'cross',
lineStyle: {
type : 'dashed',
width : 1
}
},
zlevel: 1
},
legend: {
data:[]
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataZoom : {show: true},
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
xAxis : [
{
type : 'value',
scale:true,
name: '迭代次数'
}
],
yAxis : [
{
type : 'value',
scale:true,
name: 'cost'
}
],
series : [
{
name:'遗传算法',
type:'scatter',
large: true,
symbolSize: 3,
data: (function () {
let d = []
for (let itIndex=0; itIndex<iteratorNum; itIndex++) {
for (let chromosomeIndex=0; chromosomeIndex<chromosomeNum; chromosomeIndex++) {
d.push([itIndex, resultDataGa[itIndex][chromosomeIndex]])
}
}
return d
})()
}
]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
// 蚁群算法
/** 蚂蚁的数量 */
let antNum = 100;
/** 信息素矩阵(记录每条路径上当前信息素含量,初始状态下均为0) */
let pheromoneMatrix = [];
/** 最大信息素的下标矩阵(存储当前信息素矩阵中每行最大信息素的下标) */
let maxPheromoneMatrix = [];
/** 一次迭代中,随机分配的蚂蚁临界编号(该临界点之前的蚂蚁采用最大信息素下标,而该临界点之后的蚂蚁采用随机分配) */
let criticalPointMatrix = [];
/** 顾客处理cost结果集([迭代次数][蚂蚁编号]) */
// let resultData = [];
/** 每次迭代信息素衰减的比例 */
let p = 0.5;
/** 每次经过,信息素增加的比例 */
let q = 2;
/**
* 渲染视图
* @param resultData
*/
function drawAca(resultData) {
// 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.getElementById('aca'));
// 指定图表的配置项和数据
let option = {
title: {
text: '基于蚁群算法的设施容量问题'
},
tooltip : {
trigger: 'axis',
showDelay : 0,
axisPointer:{
show: true,
type : 'cross',
lineStyle: {
type : 'dashed',
width : 1
}
},
zlevel: 1
},
legend: {
data:['传统蚁群算法']
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataZoom : {show: true},
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
xAxis : [
{
type : 'value',
scale:true,
name: '迭代次数'
}
],
yAxis : [
{
type : 'value',
scale:true,
name: '总cost'
}
],
series : [
{
name:'蚁群算法',
type:'scatter',
large: true,
symbolSize: 3,
data: (function () {
let d = [];
for (let itIndex=0; itIndex<iteratorNum; itIndex++) {
for (let antIndex=0; antIndex<antNum; antIndex++) {
d.push([itIndex, resultDataAca[itIndex][antIndex]]);
}
}
return d;
})()
},
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
/**
* 初始化 顾客集合、处理设施集合
* @param _facilities 设施参数
* @param _demands 顾客的要求
* @param _assignmentCost 顾客到指定设施的cost
*/
function startAca(_facilities, _demands, _assignmentCost) {
// 初始化设施capacity集合
facilities = _facilities.reduce((all, now) => all.concat(Number(now.split(' ')[0])), [])
openCost = _facilities.reduce((all, now) => all.concat(Number(now.split(' ')[1])), [])
customers= _demands.reduce((all, now) => all.concat(now.split(' ').filter(n => n).map(n =>Number(n))), [])
_assignmentCost.forEach(_assign => {
assignment.push(_assign.split(' ').filter(n => n).map(n => Number(n)))
})
/* console.log(facilities, openCost, customers, assignment)
// 执行蚁群算法
aca();
// 渲染视图
draw(resultData);
// console.log(resultData); */
}
// startGa(lines.slice(1, facilityNum+1), lines.slice(facilityNum+1, facilityNum+1+customerNum/10), lines.slice(facilityNum+1+customerNum/10))
/**
* 初始化信息素矩阵(全为0)
* @param customerNum 顾客数量
* @param facilityNum 设施数量
*/
function initPheromoneMatrix(customerNum, facilityNum) {
for (let i=0; i<customerNum; i++) {
let pheromoneMatrix_i = [];
for (let j=0; j<facilityNum; j++) {
pheromoneMatrix_i.push(1);
}
pheromoneMatrix.push(pheromoneMatrix_i);
}
}
/**
* 初始化一个二维数组
* @param n 行数
* @param m 列数
* @param defaultNum 默认值
*/
function initMatrix(n, m, defaultNum) {
let matrix = [];
for (let i=0; i<n; i++) {
let matrix_i = [];
for (let j=0; j<m; j++) {
matrix_i.push(defaultNum);
}
matrix.push(matrix_i);
}
return matrix
}
/**
* 将第customerCount个顾客分配给某一个设施处理
* @param antCount 蚂蚁编号
* @param customerCount 顾客编号
* @param Object usedCapacity 已经启用的设施
*/
function assignOneCustomer(antCount, customerCount, usedCapacity) {
// 若当前蚂蚁编号在临界点之前,则采用最大信息素的分配方式
// 若当前蚂蚁编号在临界点之后,则采用随机分配方式
let index = criticalPointMatrix.length && antCount <= criticalPointMatrix[customerCount]
? maxPheromoneMatrix[customerCount]
: random(0, facilityNum-1)
// 看index设施剩余容量是否还能满足这顾客
if (!usedCapacity[index]) {
usedCapacity[index] = customers[customerCount]
}
// console.log(usedCapacity[index], customers[customerCount], facilities[index])
while (usedCapacity[index]+customers[customerCount] > facilities[index] ) {
console.log(281)
index = random(0, facilityNum-1)
}
usedCapacity[index] = Number(customers[customerCount]) + Number(usedCapacity[index])
return index
}
/**
* 计算一次迭代中,所有蚂蚁的顾客处理cost
* @param pathMatrix_allAnt 所有蚂蚁的路径
*/
function calCost_OneIt(pathMatrix_allAnt) {
let cost_allAnt = []
for (let antIndex=0; antIndex<pathMatrix_allAnt.length; antIndex++) {
// 获取第antIndex只蚂蚁的行走路径
let pathMatrix = pathMatrix_allAnt[antIndex]
let cost = 0
// 记录开门的设施
let usedFacilities= {}
for (let facilityIndex = 0; facilityIndex < facilityNum; facilityIndex++) {
// 计算设施customerIndex的顾客处理cost
for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
// 计算一只蚂蚁分配的路线所有花销
if (pathMatrix[customerIndex][facilityIndex]) {
cost += assignment[customerIndex][facilityIndex]
if (!usedFacilities[facilityIndex]) {
usedFacilities[facilityIndex] = 1
cost = Number(cost) + Number(openCost[facilityIndex])
}
}
}
}
cost_allAnt.push(cost)
}
return cost_allAnt
}
/**
* 更新信息素
* @param pathMatrix_allAnt 本次迭代中所有蚂蚁的行走路径
* @param pheromoneMatrix 信息素矩阵
* @param costArray_oneIt 本次迭代的顾客处理cost的结果集
*/
function updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, costArray_oneIt) {
// 所有信息素均衰减p%
for (let i = 0; i < customerNum; i++) {
for (let j = 0; j < facilityNum; j++) {
pheromoneMatrix[i][j] *= p;
}
}
// 找出总cost最小的蚂蚁编号
let mincost = Number.MAX_VALUE;
let minIndex = -1;
for (let antIndex = 0; antIndex < antNum; antIndex++) {
if (costArray_oneIt[antIndex] < mincost) {
mincost = costArray_oneIt[antIndex];
minIndex = antIndex;
}
}
// 将本次迭代中最优路径的信息素增加q%
for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
for (let facilityIndex = 0; facilityIndex < facilityNum; facilityIndex++) {
if (pathMatrix_allAnt[minIndex][customerIndex][facilityIndex] == 1) {
pheromoneMatrix[customerIndex][facilityIndex] *= q;
}
}
}
maxPheromoneMatrix = [];
criticalPointMatrix = [];
for (let customerIndex = 0; customerIndex < customerNum; customerIndex++) {
let maxPheromone = pheromoneMatrix[customerIndex][0]
let maxIndex = 0
let sumPheromone = pheromoneMatrix[customerIndex][0]
let isAllSame = true
for (let facilityIndex = 1; facilityIndex < facilityNum; facilityIndex++) {
if (pheromoneMatrix[customerIndex][facilityIndex] > maxPheromone) {
maxPheromone = pheromoneMatrix[customerIndex][facilityIndex]
maxIndex = facilityIndex
}
if (pheromoneMatrix[customerIndex][facilityIndex] != pheromoneMatrix[customerIndex][facilityIndex-1]){
isAllSame = false
}
sumPheromone += pheromoneMatrix[customerIndex][facilityIndex]
}
// 若本行信息素全都相等,则随机选择一个作为最大信息素
if (isAllSame==true) {
maxIndex = random(0, facilityNum-1)
maxPheromone = pheromoneMatrix[customerIndex][maxIndex]
}
// 将本行最大信息素的下标加入maxPheromoneMatrix
maxPheromoneMatrix.push(maxIndex)
// 将本次迭代的蚂蚁临界编号加入criticalPointMatrix(该临界点之前的蚂蚁的顾客分配根据最大信息素原则,而该临界点之后的蚂蚁采用随机分配策略)
criticalPointMatrix.push(Math.round(antNum * (maxPheromone/sumPheromone)))
}
}
/**
* 迭代搜索
* @param iteratorNum 迭代次数
* @param antNum 蚂蚁数量
*/
function acaSearch(iteratorNum, antNum) {
for (let itCount=0; itCount<iteratorNum; itCount++) {
// 本次迭代中,所有蚂蚁的路径
let pathMatrix_allAnt = []
for (let antCount=0; antCount<antNum; antCount++) {
let usedCapacity= {}
// console.log(antCount)
// 第antCount只蚂蚁的分配策略(pathMatrix[i][j]表示第antCount只蚂蚁将i顾客分配给j设施处理)
let pathMatrix_oneAnt = initMatrix(customerNum, facilityNum, 0)
for (let cCount=0; cCount<customerNum; cCount++) {
// console.log(cCount)
// 将第cCount个customer分配给第fcount个facility处理
let fCount = assignOneCustomer(antCount, cCount, usedCapacity)
pathMatrix_oneAnt[cCount][fCount] = 1
}
// 将当前蚂蚁的路径加入pathMatrix_allAnt
pathMatrix_allAnt.push(pathMatrix_oneAnt)
}
// 计算 本次迭代中 所有蚂蚁 的花销
let costArray_OneIt = calCost_OneIt(pathMatrix_allAnt)
// 将本地迭代中 所有蚂蚁的 cost加入总结果集
resultDataAca.push(costArray_OneIt)
// 更新信息素
updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, costArray_OneIt)
}
}
/**
* 蚁群算法
*/
function aca() {
// 初始化信息素矩阵
initPheromoneMatrix(customerNum, facilityNum);
// 迭代搜索
acaSearch(iteratorNum, antNum);
}
样例结果
instances | 蚁群 result | time (ms) | 遗传result | time(ms) |
---|---|---|---|---|
p1 | 19372 | 1600 | 16678 | 238 |
p2 | 17317 | 2703 | 14778 | 228 |
p3 | 21441 | 1234 | 15478 | 232 |
p4 | 21599 | 2666 | 17030 | 231 |
p5 | 19611 | 2168 | 16337 | 340 |
p6 | 17155 | 2159 | 17121 | 272 |
p7 | 17467 | 4542 | 19125 | 260 |
p8 | 21287 | 2139 | 20155 | 277 |
p9 | 19448 | 480 | 14098 | 288 |
p10 | 19516 | 407 | 12950 | 281 |
p11 | 18826 | 563 | 16505 | 233 |
p12 | 20569 | 1460 | 16903 | 230 |
p13 | 24688 | 296 | 18173 | 322 |
p14 | 19262 | 454 | 15937 | 240 |
p15 | 23867 | 390 | 19658 | 243 |
p16 | 29112 | 329 | 23100 | 238 |
p17 | 24209 | 246 | 18957 | 254 |
p18 | 21592 | 230 | 13945 | 237 |
p19 | 24621 | 259 | 18640 | 232 |
p20 | 27110 | 273 | 20089 | 233 |
p21 | 20251 | 244 | 15620 | 239 |
p22 | 20937 | 228 | 16458 | 240 |
p23 | 24175 | 302 | 17608 | 234 |
p24 | 27554 | 363 | 22115 | 243 |
p25 | 86414 | 1121 | 68169 | 678 |
p26 | 85532 | 733 | 61299 | 694 |
p40 | 97156 | 677 | 75392 | 711 |
p41 | 13944 | 939 | 11616 | 247 |
p42 | 17048 | 414 | 13728 | 338 |
p43 | 17932 | 410 | 15513 | 389 |
p44 | 17886 | 1203 | 13675 | 262 |
p45 | 18612 | 542 | 16218 | 319 |
p46 | 21161 | 368 | 15371 | 360 |
p47 | 16224 | 1050 | 13413 | 266 |
p48 | 18334 | 507 | 14585 | 327 |
p49 | 18470 | 402 | 15538 | 377 |
p50 | 18355 | 915 | 15901 | 269 |
p51 | 22212 | 555 | 18516 | 386 |
p52 | 22301 | 1522 | 19970 | 269 |
p53 | 26891 | 715 | 23231 | 389 |
p54 | 20466 | 1632 | 17575 | 271 |
p55 | 25114 | 740 | 20959 | 394 |
p56 | 81243 | 1194 | 70821 | 891 |
p57 | 88284 | 1412 | 80706 | 876 |
p58 | 108619 | 1149 | 100460 | 864 |
p59 | 92969 | 1883 | 85197 | 869 |
p60 | 80730 | 983 | 70333 | 887 |
p61 | 92013 | 942 | 81345 | 909 |
p62 | 107893 | 972 | 101119 | 858 |
p63 | 93974 | 959 | 86221 | 854 |
p64 | 80396 | 957 | 69039 | 860 |
p65 | 89248 | 970 | 74799 | 886 |