├── ACA.js ├── GA.js ├── LICENSE ├── README.md ├── aca.html ├── common.js ├── ga.html └── img ├── WechatIMG36.jpeg ├── result.png └── wechat.png /ACA.js: -------------------------------------------------------------------------------- 1 | 2 | /** 任务集合(tasks[i]表示第i个任务的长度) */ 3 | var tasks = []; 4 | // 任务数量 5 | var taskNum = 100; 6 | 7 | /** 处理节点集合(nodes[i]表示第i个处理节点的处理速度) */ 8 | var nodes = []; 9 | // 处理节点数量 10 | var nodeNum = 10; 11 | 12 | /** 任务长度取值范围 */ 13 | var taskLengthRange = [10,100]; 14 | /** 节点处理速度取值范围 */ 15 | var nodeSpeendRange = [10,100]; 16 | 17 | /** 迭代次数 */ 18 | var iteratorNum = 100; 19 | 20 | /** 蚂蚁的数量 */ 21 | var antNum = 100; 22 | 23 | /** 任务处理时间矩阵(记录单个任务在不同节点上的处理时间) */ 24 | var timeMatrix = []; 25 | 26 | /** 信息素矩阵(记录每条路径上当前信息素含量,初始状态下均为0) */ 27 | var pheromoneMatrix = []; 28 | 29 | /** 最大信息素的下标矩阵(存储当前信息素矩阵中每行最大信息素的下标) */ 30 | var maxPheromoneMatrix = []; 31 | 32 | /** 一次迭代中,随机分配的蚂蚁临界编号(该临界点之前的蚂蚁采用最大信息素下标,而该临界点之后的蚂蚁采用随机分配) */ 33 | var criticalPointMatrix = []; 34 | 35 | /** 任务处理时间结果集([迭代次数][蚂蚁编号]) */ 36 | var resultData = []; 37 | 38 | /** 每次迭代信息素衰减的比例 */ 39 | var p = 0.5; 40 | 41 | /** 每次经过,信息素增加的比例 */ 42 | var q = 2; 43 | 44 | /** 45 | * 参数校验 46 | * @param _taskNum 任务数量 47 | * @param _nodeNum 节点数量 48 | * @param _iteratorNum 迭代次数 49 | * @param _antNum 蚂蚁数量 50 | */ 51 | function checkParam(_taskNum, _nodeNum, _iteratorNum, _antNum) { 52 | if (isNaN(_taskNum)) { 53 | alert("任务数量必须是数字!"); 54 | return false; 55 | } 56 | if (isNaN(_nodeNum)) { 57 | alert("节点数量必须是数字!"); 58 | return false; 59 | } 60 | if (isNaN(_iteratorNum)) { 61 | alert("迭代次数必须是数字!"); 62 | return false; 63 | } 64 | if (isNaN(_antNum)) { 65 | alert("蚂蚁数量必须是数字!"); 66 | return false; 67 | } 68 | 69 | taskNum = _taskNum; 70 | nodeNum = _nodeNum; 71 | iteratorNum = _iteratorNum; 72 | antNum = _antNum; 73 | 74 | return true; 75 | } 76 | 77 | /** 78 | * 渲染视图 79 | * @param resultData 80 | */ 81 | function draw(resultData) { 82 | // 基于准备好的dom,初始化echarts实例 83 | var myChart = echarts.init(document.getElementById('main')); 84 | 85 | // 指定图表的配置项和数据 86 | var option = { 87 | title: { 88 | text: '基于蚁群算法的负载均衡调度策略' 89 | }, 90 | tooltip : { 91 | trigger: 'axis', 92 | showDelay : 0, 93 | axisPointer:{ 94 | show: true, 95 | type : 'cross', 96 | lineStyle: { 97 | type : 'dashed', 98 | width : 1 99 | } 100 | }, 101 | zlevel: 1 102 | }, 103 | legend: { 104 | data:['传统蚁群算法','优化的蚁群算法'] 105 | }, 106 | toolbox: { 107 | show : true, 108 | feature : { 109 | mark : {show: true}, 110 | dataZoom : {show: true}, 111 | dataView : {show: true, readOnly: false}, 112 | restore : {show: true}, 113 | saveAsImage : {show: true} 114 | } 115 | }, 116 | xAxis : [ 117 | { 118 | type : 'value', 119 | scale:true, 120 | name: '迭代次数' 121 | } 122 | ], 123 | yAxis : [ 124 | { 125 | type : 'value', 126 | scale:true, 127 | name: '任务处理时间' 128 | } 129 | ], 130 | series : [ 131 | { 132 | name:'传统蚁群算法', 133 | type:'scatter', 134 | large: true, 135 | symbolSize: 3, 136 | data: (function () { 137 | var d = []; 138 | for (var itIndex=0; itIndex maxPheromone) { 254 | // maxPheromone = pheromoneMatrix[taskCount][j]; 255 | // maxIndex = j; 256 | // } 257 | // if (pheromoneMatrix[taskCount][j] != pheromoneMatrix[taskCount][j-1]) { 258 | // isAllSame = false; 259 | // } 260 | // } 261 | // 262 | // // 若该行信息素全都相同,则随机选择一个处理节点 263 | // if (isAllSame==true) { 264 | // maxIndex = random(0, nodeNum-1); 265 | // } 266 | // 267 | // return maxIndex; 268 | } 269 | 270 | /** 271 | * 计算一次迭代中,所有蚂蚁的任务处理时间 272 | * @param pathMatrix_allAnt 所有蚂蚁的路径 273 | */ 274 | function calTime_oneIt(pathMatrix_allAnt) { 275 | var time_allAnt = []; 276 | for (var antIndex=0; antIndex maxTime) { 292 | maxTime = time; 293 | } 294 | } 295 | 296 | time_allAnt.push(maxTime); 297 | } 298 | return time_allAnt; 299 | } 300 | 301 | /** 302 | * 更新信息素 303 | * @param pathMatrix_allAnt 本次迭代中所有蚂蚁的行走路径 304 | * @param pheromoneMatrix 信息素矩阵 305 | * @param timeArray_oneIt 本次迭代的任务处理时间的结果集 306 | */ 307 | function updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, timeArray_oneIt) { 308 | // 所有信息素均衰减p% 309 | for (var i=0; i maxPheromone) { 355 | maxPheromone = pheromoneMatrix[taskIndex][nodeIndex]; 356 | maxIndex = nodeIndex; 357 | } 358 | 359 | if (pheromoneMatrix[taskIndex][nodeIndex] != pheromoneMatrix[taskIndex][nodeIndex-1]){ 360 | isAllSame = false; 361 | } 362 | 363 | sumPheromone += pheromoneMatrix[taskIndex][nodeIndex]; 364 | } 365 | 366 | // 若本行信息素全都相等,则随机选择一个作为最大信息素 367 | if (isAllSame==true) { 368 | maxIndex = random(0, nodeNum-1); 369 | maxPheromone = pheromoneMatrix[taskIndex][maxIndex]; 370 | } 371 | 372 | // 将本行最大信息素的下标加入maxPheromoneMatrix 373 | maxPheromoneMatrix.push(maxIndex); 374 | 375 | // 将本次迭代的蚂蚁临界编号加入criticalPointMatrix(该临界点之前的蚂蚁的任务分配根据最大信息素原则,而该临界点之后的蚂蚁采用随机分配策略) 376 | criticalPointMatrix.push(Math.round(antNum * (maxPheromone/sumPheromone))); 377 | } 378 | } 379 | 380 | /** 381 | * 迭代搜索 382 | * @param iteratorNum 迭代次数 383 | * @param antNum 蚂蚁数量 384 | */ 385 | function acaSearch(iteratorNum, antNum) { 386 | for (var itCount=0; itCount1) { 108 | alert("cp值必须为数字!并且在0~1之间!"); 109 | return false; 110 | } 111 | 112 | taskNum = _taskNum; 113 | nodeNum = _nodeNum; 114 | iteratorNum = _iteratorNum; 115 | chromosomeNum = _chromosomeNum; 116 | cp = _cp; 117 | crossoverMutationNum = chromosomeNum - chromosomeNum*_cp; 118 | 119 | return true; 120 | } 121 | 122 | 123 | /** 124 | * 计算 染色体适应度 125 | * @param chromosomeMatrix 126 | */ 127 | function calAdaptability(chromosomeMatrix) { 128 | adaptability = []; 129 | 130 | // 计算每条染色体的任务长度 131 | for (var chromosomeIndex=0; chromosomeIndex maxLength) { 142 | maxLength = sumLength; 143 | } 144 | } 145 | 146 | // 适应度 = 1/任务长度 147 | adaptability.push(1/maxLength); 148 | } 149 | } 150 | 151 | /** 152 | * 计算自然选择概率 153 | * @param adaptability 154 | */ 155 | function calSelectionProbability(adaptability) { 156 | selectionProbability = []; 157 | 158 | // 计算适应度总和 159 | var sumAdaptability = 0; 160 | for (var i=0; i matrix[j][1]) { 232 | var temp = matrix[j-1]; 233 | matrix[j-1] = matrix[j]; 234 | matrix[j] = temp; 235 | } 236 | } 237 | } 238 | 239 | // 取最大的n个元素 240 | var maxIndexArray = []; 241 | for (var i=matrix.length-1; i>matrix.length-n-1; i--) { 242 | maxIndexArray.push(matrix[i][0]); 243 | } 244 | 245 | return maxIndexArray; 246 | } 247 | 248 | /** 249 | * 复制(复制上一代中优良的染色体) 250 | * @param chromosomeMatrix 上一代染色体矩阵 251 | * @param newChromosomeMatrix 新一代染色体矩阵 252 | */ 253 | function copy(chromosomeMatrix, newChromosomeMatrix) { 254 | // 寻找适应度最高的N条染色体的下标(N=染色体数量*复制比例) 255 | var chromosomeIndexArr = maxN(adaptability, chromosomeNum*cp); 256 | 257 | // 复制 258 | for (var i=0; i maxLength) { 284 | maxLength = sumLength; 285 | } 286 | } 287 | 288 | timeArray_oneIt.push(maxLength); 289 | } 290 | resultData.push(timeArray_oneIt); 291 | } 292 | 293 | /** 294 | * 繁衍新一代染色体 295 | * @param chromosomeMatrix 上一代染色体 296 | */ 297 | function createGeneration(chromosomeMatrix) { 298 | 299 | // 第一代染色体,随机生成 300 | if (chromosomeMatrix == null || chromosomeMatrix == undefined) { 301 | var newChromosomeMatrix = []; 302 | for (var chromosomeIndex=0; chromosomeIndex= rand) { 341 | return i; 342 | } 343 | } 344 | } 345 | 346 | 347 | /** 348 | * 变异 349 | * @param newChromosomeMatrix 新一代染色体矩阵 350 | */ 351 | function mutation(newChromosomeMatrix) { 352 | // 随机找一条染色体 353 | var chromosomeIndex = random(0, crossoverMutationNum-1); 354 | 355 | // 随机找一个任务 356 | var taskIndex = random(0, taskNum-1); 357 | 358 | // 随机找一个节点 359 | var nodeIndex = random(0, nodeNum-1); 360 | 361 | newChromosomeMatrix[chromosomeIndex][taskIndex] = nodeIndex; 362 | 363 | return newChromosomeMatrix; 364 | } 365 | 366 | /** 367 | * 渲染视图 368 | * @param resultData 369 | */ 370 | function draw(resultData) { 371 | // 基于准备好的dom,初始化echarts实例 372 | var myChart = echarts.init(document.getElementById('main')); 373 | 374 | // 指定图表的配置项和数据 375 | var option = { 376 | title: { 377 | text: '基于遗传算法的负载均衡调度策略' 378 | }, 379 | tooltip : { 380 | trigger: 'axis', 381 | showDelay : 0, 382 | axisPointer:{ 383 | show: true, 384 | type : 'cross', 385 | lineStyle: { 386 | type : 'dashed', 387 | width : 1 388 | } 389 | }, 390 | zlevel: 1 391 | }, 392 | legend: { 393 | data:['遗传算法'] 394 | }, 395 | toolbox: { 396 | show : true, 397 | feature : { 398 | mark : {show: true}, 399 | dataZoom : {show: true}, 400 | dataView : {show: true, readOnly: false}, 401 | restore : {show: true}, 402 | saveAsImage : {show: true} 403 | } 404 | }, 405 | xAxis : [ 406 | { 407 | type : 'value', 408 | scale:true, 409 | name: '迭代次数' 410 | } 411 | ], 412 | yAxis : [ 413 | { 414 | type : 'value', 415 | scale:true, 416 | name: '任务处理时间' 417 | } 418 | ], 419 | series : [ 420 | { 421 | name:'遗传算法', 422 | type:'scatter', 423 | large: true, 424 | symbolSize: 3, 425 | data: (function () { 426 | var d = []; 427 | for (var itIndex=0; itIndex 蚂蚁几乎没有视力,但他们却能够在黑暗的世界中找到食物,而且能够找到一条从洞穴到食物的最短路径。它们是如何做到的呢? 3 | 4 | ## 蚂蚁寻找食物的过程 5 | 单只蚂蚁的行为及其简单,行为数量在10种以内,但成千上万只蚂蚁组成的蚁群却能拥有巨大的智慧,这离不开它们信息传递的方式——信息素。 6 | 7 | 蚂蚁在行走过程中会释放一种称为“信息素”的物质,用来标识自己的行走路径。在寻找食物的过程中,根据信息素的浓度选择行走的方向,并最终到达食物所在的地方。 8 | 9 | 信息素会随着时间的推移而逐渐挥发。 10 | 11 | 在一开始的时候,由于地面上没有信息素,因此蚂蚁们的行走路径是随机的。蚂蚁们在行走的过程中会不断释放信息素,标识自己的行走路径。随着时间的推移,有若干只蚂蚁找到了食物,此时便存在若干条从洞穴到食物的路径。由于蚂蚁的行为轨迹是随机分布的,因此在单位时间内,短路径上的蚂蚁数量比长路径上的蚂蚁数量要多,从而蚂蚁留下的信息素浓度也就越高。这为后面的蚂蚁们提供了强有力的方向指引,越来越多的蚂蚁聚集到最短的路径上去。 12 | 13 | ## 什么是蚁群算法? 14 | 蚁群算法就是模拟蚂蚁寻找食物的过程,它能够求出从原点出发,经过若干个给定的需求点,最终返回原点的最短路径。这也就是著名的旅行商问题(Traveling Saleman Problem,TSP)。 15 | 16 | 本文使用蚁群算法来解决分布式环境下的负载均衡调度问题。 17 | 18 | ## 蚁群算法的应用——负载均衡调度 19 | 集群模式是目前较为常用的一种部署结构,也就是当单机处理能力无法满足业务需求,那么就增加处理节点,并由一个负载均衡器负责请求的调度。然而对于一个庞大系统而言,情况往往比较复杂。集群中节点的处理能力往往各不相同,而且不同任务的处理复杂度也不尽相同。那么负载均衡器如何进行任务分配,使得集群性能达到最优?资源利用率达到最高呢?这是一个极具挑战又很有价值的问题。 20 | 21 | // TODO 增加目前的负载均衡调度算法的优缺点分析 22 | 23 | 本文我们就采用蚁群算法来解决这一问题。 24 | 25 | ## 数学建模 26 | 在开始之前,我们首先需要将“负载均衡调度”这个问题进行数学建模,量化各项指标,并映射到蚁群算法中。 27 | 28 | ## 问题描述 29 | > 求一种最优的任务分配策略,能够将N个长度不等的任务按照某一种策略分配给M个处理能力不同的服务器节点,并且N个任务的完成时间最短。 30 | 31 | 在这个问题中,我们将所有任务的完成时间作为衡量分配策略优良的指标。每一种分配策略都是这个问题的一个可行解。那么具有最小完成时间的分配策略就是这个问题的最优解。 32 | 33 | ![title](img/WechatIMG36.jpeg) 34 | 35 | 36 | ## 参数定义 37 | ```js 38 | var tasks = []; 39 | var taskNum = 100; 40 | ``` 41 | - tasks:任务数组,数组的下标表示任务的编号,数组的值表示任务的长度。比如:tasks[0]=10表示第一个任务的任务长度是10. 42 | - taskNum:任务的数量,也就是tasks数组的长度。这里为了提高代码的可读性才专门使用taskNum来表示任务数量。 43 | 44 | ```js 45 | var nodes = []; 46 | var nodeNum = 10; 47 | ``` 48 | - nodes:处理节点的数组。数组的下标表示处理节点的编号,数组值表示节点的处理速度。比如:nodes[0]=10表示第1个处理节点的处理速度为10. 49 | - nodeNum:处理节点的数量,也就是nodes数组的长度。这里也是为了提高代码的可读性才专门使用nodeNum来表示节点的数量。 50 | 51 | ```js 52 | var iteratorNum; 53 | var antNum; 54 | ``` 55 | - iteratorNum:蚁群算法一共需要迭代的次数,每次迭代都有antNum只蚂蚁进行任务分配。 56 | - antNum:每次迭代中蚂蚁的数量。每只蚂蚁都是一个任务调度者,每次迭代中的每一只蚂蚁都需要完成所有任务的分配,这也就是一个可行解。 57 | 58 | ```js 59 | var timeMatrix = []; 60 | ``` 61 | - 任务处理时间矩阵。 62 | - 它是一个二维矩阵。比如:timeMatrix[i][j]就表示第i个任务分配给第j个节点所需的处理时间。 63 | - 这个矩阵是基于tasks数组和nodes数组计算而来的。比如task[i]表示第i个任务的任务长度,nodes[j]表示第j个节点的处理速度。所以,timeMatrix[i][j]=task[i]/nodes[j]. 64 | 65 | ```js 66 | var pheromoneMatrix = []; 67 | var maxPheromoneMatrix = []; 68 | var criticalPointMatrix = []; 69 | ``` 70 | - pheromoneMatrix:信息素矩阵 71 | - 它是一个二维矩阵,用于记录任务i分配给节点j这条路径上的信息素浓度。 72 | - 比如:pheromoneMatrix[i][j]=0.5就表示任务i分配给节点j这条路径上的信息素浓度为0.5 73 | - maxPheromoneMatrix:pheromoneMatrix矩阵的每一行中最大信息素的下标。 74 | - 比如:maxPheromoneMatrix[0]=5表示pheromoneMatrix第0行的所有信息素中,最大信息素的下标是5. 75 | - criticalPointMatrix:在一次迭代中,采用随机分配策略的蚂蚁的临界编号。 76 | - 比如:如果将蚂蚁数量设为10,那么每次迭代中都有10只蚂蚁完成所有任务的分配工作。并且分配过程是按照蚂蚁编号从小到大的顺序进行的(蚂蚁从0开始编号)。如果criticalPointMatrix[0]=5,那么也就意味着,在分配第0个任务的时候,编号是0~5的蚂蚁根据信息素浓度进行任务分配(即:将任务分配给本行中信息素浓度最高的节点处理),6~9号蚂蚁则采用随机分配的方式(即:将任务随机分配给任意一个节点处理)。 77 | - **为什么要这么做?** 78 | 如果每只蚂蚁都将任务分配给信息素浓度最高的节点处理,那么就会出现停滞现象。也就是算法过早地收敛至一个局部最优解,无法发现全局最优解。 79 | 因此需要一部分蚂蚁遵循信息素最高的分配策略,还需要一部分蚂蚁遵循随机分配的策略,以发现新的局部最优解。 80 | 81 | ```js 82 | var p = 0.5; 83 | var q = 2; 84 | ``` 85 | - p:每完成一次迭代后,信息素衰减的比例。 86 | 我们知道,在真实的蚁群中,蚂蚁分泌的信息素会随着时间的推移而渐渐衰减。那么在算法中,我们使得信息素每完成一次迭代后进行衰减,但在一次迭代过程中,信息素浓度保持不变。 87 | - q:蚂蚁每次经过一条路径,信息素增加的比例。 88 | 我们也知道,在真实的蚁群中,蚂蚁会在行进过程中分泌信息素。那么在算法中,我们使得算法每完成一次迭代后,就将蚂蚁经过的路径上增加信息素q,但在一次迭代过程中,信息素浓度不变。 89 | 90 | 91 | ## 算法初始化 92 | ```js 93 | // 初始化任务集合 94 | tasks = initRandomArray(_taskNum, taskLengthRange); 95 | 96 | // 初始化节点集合 97 | nodes = initRandomArray(_nodeNum, nodeSpeendRange); 98 | ``` 99 | 在正式开始之前,我们需要初始化任务数组和节点数组。这里采用随机赋值的方式,我们给tasks随机创建100个任务,每个任务的长度是10~100之间的随机整数。再给nodes随机创建10个节点,每个节点的处理速度是10~100之间的随机整数。 100 | 101 | OK,准备工作完成,下面来看蚁群算法的实现。 102 | 103 | ## 蚁群算法 104 | ```js 105 | /** 106 | * 蚁群算法 107 | */ 108 | function aca() { 109 | // 初始化任务执行时间矩阵 110 | initTimeMatrix(tasks, nodes); 111 | 112 | // 初始化信息素矩阵 113 | initPheromoneMatrix(taskNum, nodeNum); 114 | 115 | // 迭代搜索 116 | acaSearch(iteratorNum, antNum); 117 | } 118 | ``` 119 | 正如你所看到的,蚁群算法并不复杂,总体而言就是这三部: 120 | 121 | - 初始化任务执行时间矩阵 122 | - 初始化信息素矩阵 123 | - 迭代搜索 124 | 125 | 当然,第一第二步都较为简单,相对复杂的代码在“迭代搜索”中。那么下面我们就分别来看一下这三个步骤的实现过程。 126 | 127 | ## 初始化任务执行时间矩阵 128 | ```js 129 | /** 130 | * 初始化任务处理时间矩阵 131 | * @param tasks 任务(长度)列表 132 | * @param nodes 节点(处理速度)列表 133 | */ 134 | function initTimeMatrix(tasks, nodes) { 135 | for (var i=0; i 注意:我们将负载均衡调度过程中的一次任务分配当作蚁群算法中一条路径。如:我们将“任务i分配给节点j”这一动作,当作蚂蚁从任务i走向节点j的一条路径。因此,pheromoneMatrix[i][j]就相当于i——>j这条路径上的信息素浓度。 174 | 175 | ## 迭代搜索过程 176 | ```js 177 | /** 178 | * 迭代搜索 179 | * @param iteratorNum 迭代次数 180 | * @param antNum 蚂蚁数量 181 | */ 182 | function acaSearch(iteratorNum, antNum) { 183 | for (var itCount=0; itCount 注意:收敛速度也是衡量算法优良的一个重要指标。比如算法1迭代10次就能找到全局最优解,而算法2迭代1000次才能找到全局最优解。所以算法1的收敛速度要优于算法2. 221 | 222 | 下面介绍上述算法的执行流程。 223 | 224 | 蚁群算法一共要进行iteratorNum次迭代,每次迭代中,所有蚂蚁都需要完成所有任务的分配。因此上述算法采用了三层for循环,第一层用于迭代次数的循环,在本算法中一共要循环1000次;第二层用于蚂蚁的循环,本算法一共有10只蚂蚁,因此需要进行10次循环;第三层用于所有任务的循环,本算法一共有100个任务,因此需要循环100次,每一次循环,都将当前任务按照某一种策略分配给某一个节点,并在pathMatrix_oneAnt矩阵中记录蚂蚁的分配策略。 225 | 226 | pathMatrix_oneAnt是一个二维矩阵,所有元素要么是0要么是1.比如:pathMatrix_oneAnt[i][j]=1就表示当前蚂蚁将任务i分配给了节点j处理,pathMatrix_oneAnt[i][j]=0表示任务i没有分配给节点j处理。该矩阵的每一行都有且仅有一个元素为1,其他元素均为0. 227 | 228 | 每一只蚂蚁当完成这100个任务的分配之后,就会产生一个pathMatrix_oneAnt矩阵,用于记录该只蚂蚁的分配策略。那么当10只蚂蚁均完成任务的分配后,就会产生一个pathMatrix矩阵。这是一个三维矩阵,第一维记录了蚂蚁的编号,第二维表示任务的下标,第三维表示节点的编号,从而pathMatrix[x][i][j]=1就表示编号为x的蚂蚁将任务i分配给了节点j处理;pathMatrix[x][i][j]=0就表示编号为x的蚂蚁没有将任务i分配给了节点j处理。 229 | 230 | 这10只蚂蚁完成一次任务的分配也被称为一次迭代。每完成一次迭代后,都要使用calTime_oneIt函数在计算本次迭代中,所有蚂蚁的任务处理时间,并记录在timeArray_oneIt矩阵中。 231 | 232 | 在每次迭代完成前,还需要使用updatePheromoneMatrix函数来更新信息素矩阵。 233 | 234 | 下面就分别详细介绍迭代搜索过程中的三个重要函数: 235 | 236 | - 任务分配函数:assignOneTask 237 | - 任务处理时间计算函数:calTime_oneIt 238 | - 更新信息素函数:updatePheromoneMatrix 239 | 240 | ## 任务分配函数 241 | ```js 242 | /** 243 | * 将第taskCount个任务分配给某一个节点处理 244 | * @param antCount 蚂蚁编号 245 | * @param taskCount 任务编号 246 | * @param nodes 节点集合 247 | * @param pheromoneMatrix 信息素集合 248 | */ 249 | function assignOneTask(antCount, taskCount, nodes, pheromoneMatrix) { 250 | 251 | // 若当前蚂蚁编号在临界点之前,则采用最大信息素的分配方式 252 | if (antCount <= criticalPointMatrix[taskCount]) { 253 | return maxPheromoneMatrix[taskCount]; 254 | } 255 | 256 | // 若当前蚂蚁编号在临界点之后,则采用随机分配方式 257 | return random(0, nodeNum-1); 258 | } 259 | ``` 260 | 261 | 任务分配函数负责将一个指定的任务按照某种策略分配给某一节点处理。分配策略一共有两种: 262 | 263 | 1. 按信息素浓度分配 264 | 也就是将任务分配给本行中信息素浓度最高的节点处理。比如:当前的任务编号是taskCount,当前的信息素浓度矩阵是pheromoneMatrix,那么任务将会分配给pheromoneMatrix[taskCount]这一行中信息素浓度最高的节点。 265 | 266 | 2. 随机分配 267 | 将任务随意分配给某一个节点处理。 268 | 269 | 那么,这两种分配策略究竟如何选择呢?答案是——根据当前蚂蚁的编号antCount。 270 | 271 | 通过上文可知,矩阵criticalPointMatrix用于记录本次迭代中,采用不同分配策略的蚂蚁编号的临界点。比如:criticalPointMatrix[i]=5就表示编号为0~5的蚂蚁在分配任务i的时候采用“按信息素浓度”的方式分配(即:将任务i分配给信息素浓度最高的节点处理);而编号为6~9的蚂蚁在分配任务i时,采用随机分配策略。 272 | 273 | ## 计算任务处理时间 274 | ```js 275 | /** 276 | * 计算一次迭代中,所有蚂蚁的任务处理时间 277 | * @param pathMatrix_allAnt 所有蚂蚁的路径 278 | */ 279 | function calTime_oneIt(pathMatrix_allAnt) { 280 | var time_allAnt = []; 281 | for (var antIndex=0; antIndex maxTime) { 297 | maxTime = time; 298 | } 299 | } 300 | 301 | time_allAnt.push(maxTime); 302 | } 303 | return time_allAnt; 304 | } 305 | ``` 306 | 307 | 每完成一次迭代,都需要计算本次迭代中所有蚂蚁的行走路径(即:所有蚂蚁的任务处理之间),并记录在time_allAnt矩阵中。 308 | 309 | 在实际的负载均衡调度中,各个节点的任务处理是并行计算的,所以,所有任务的完成时间应该是所有节点任务完成时间的最大值,并非所有任务完成时间的总和。 310 | 311 | 每完成一次迭代,就会产生一个time_allAnt矩阵,并且加入resultData矩阵中。当算法完成所有迭代后,所有蚂蚁的所有任务处理时间都被记录在resultData矩阵中,它是一个二维矩阵。比如:resultData[x][y]=10代表第x次迭代中第y只蚂蚁的任务处理时间是10. 312 | 313 | 314 | ## 更新信息素 315 | ```js 316 | /** 317 | * 更新信息素 318 | * @param pathMatrix_allAnt 本次迭代中所有蚂蚁的行走路径 319 | * @param pheromoneMatrix 信息素矩阵 320 | * @param timeArray_oneIt 本次迭代的任务处理时间的结果集 321 | */ 322 | function updatePheromoneMatrix(pathMatrix_allAnt, pheromoneMatrix, timeArray_oneIt) { 323 | // 所有信息素均衰减p% 324 | for (var i=0; i maxPheromone) { 359 | maxPheromone = pheromoneMatrix[taskIndex][nodeIndex]; 360 | maxIndex = nodeIndex; 361 | } 362 | 363 | if (pheromoneMatrix[taskIndex][nodeIndex] != pheromoneMatrix[taskIndex][nodeIndex-1]){ 364 | isAllSame = false; 365 | } 366 | 367 | sumPheromone += pheromoneMatrix[taskIndex][nodeIndex]; 368 | } 369 | 370 | // 若本行信息素全都相等,则随机选择一个作为最大信息素 371 | if (isAllSame==true) { 372 | maxIndex = random(0, nodeNum-1); 373 | maxPheromone = pheromoneMatrix[taskIndex][maxIndex]; 374 | } 375 | 376 | // 将本行最大信息素的下标加入maxPheromoneMatrix 377 | maxPheromoneMatrix.push(maxIndex); 378 | 379 | // 将本次迭代的蚂蚁临界编号加入criticalPointMatrix(该临界点之前的蚂蚁的任务分配根据最大信息素原则,而该临界点之后的蚂蚁采用随机分配策略) 380 | criticalPointMatrix.push(Math.round(antNum * (maxPheromone/sumPheromone))); 381 | } 382 | } 383 | ``` 384 | 385 | 每完成一次迭代,都需要更新信息素矩阵,这个函数的包含了如下四步: 386 | 387 | 1. 将所有信息素浓度降低p% 388 | 这个过程用来模拟信息素的挥发。 389 | 390 | 2. 找出本次迭代中最短路径,并将该条路径的信息素浓度提高q% 391 | 每次迭代,10只蚂蚁就会产生10条路径(即10种任务分配策略),我们需要找出最短路径,并将该条路径的信息素浓度提高。 392 | 393 | 3. 更新maxPheromoneMatrix矩阵 394 | 步骤1和步骤2完成后,信息素矩阵已经更新完毕。接下来需要基于这个最新的信息素矩阵,计算每行最大信息素对应的下标,即:maxPheromoneMatrix矩阵。通过上文可知,该矩阵供函数assignOneTask在分配任务时使用。 395 | 396 | 4. 更新criticalPointMatrix矩阵 397 | 紧接着需要更新criticalPointMatrix矩阵,记录采用何种任务分配策略的蚂蚁临界编号。 398 | 比如:信息素矩阵第0行的元素为pheromoneMatrix[0]={1,3,1,1,1,1,1,1,1,1},那么criticalPointMatrix[0]的计算方式如下: 399 | - 计算最大信息素的概率:最大信息素/该行所有信息素之和 400 | - 3/(1+3+1+1+1+1+1+1+1+1)=0.25 401 | - 计算蚂蚁的临界下标:蚂蚁数量*最大信息素的概率 402 | - 10*0.25=3(四舍五入) 403 | - 所以criticalPointMatrix[0]=3 404 | - 也就意味着在下一次迭代过程中,当分配任务0时,0~3号蚂蚁将该任务分配给信息素浓度最高的节点,而4~9号蚂蚁采用随机分配策略。 405 | 406 | ## 结果分析 407 | 算法的运行结果如下图所示: 408 | ![title](img/result.png) 409 | 410 | 横坐标为迭代次数,纵坐标为任务处理时间。 411 | 每个点表示一只蚂蚁的任务处理时间。上图的算法的迭代次数为100,蚂蚁数量为1000,所以每次迭代都会产生1000种任务分配方案,而每次迭代完成后都会挑选出一个当前最优方案,并提升该方案的信息素浓度,从而保证在下一次迭代中,选择该方案的概率较高。并且还使用一定概率的蚂蚁采用随机分配策略,以发现更优的方案。 412 | 413 | 从图中我们可以看到,大约迭代30次时,出现了全局最优解。 414 | 415 | ## 写在最后 416 | 所有代码我已经上传至我的Github,大家可以随意下载。 417 | https://github.com/bz51/AntColonyAlgorithm 418 | 419 | 上面一共有两个问题: 420 | 421 | - aca.html 422 | - ACA.js 423 | 424 | 蚁群算法的实现均在aca.js中,你把代码down下来之后直接在浏览器打开aca.html即可查看。欢迎各位star。也欢迎关注我的公众号,我定期分享技术干货的地方~ 425 | -------------------------------------------------------------------------------- /aca.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 蚁群算法 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取指定范围内的随机数 3 | * @param start 起点 4 | * @param end 终点 5 | * @returns {number} 6 | */ 7 | function random(start, end){ 8 | var length = end-start+1; 9 | return Math.floor(Math.random() * length + start); 10 | } 11 | 12 | /** 13 | * 创建随机数组 14 | * @param length 数组长度 15 | * @param range 数组取值范围 16 | */ 17 | function initRandomArray(length, range) { 18 | var randomArray = []; 19 | for (var i=0; i 2 | 3 | 4 | 5 | 遗传算法 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /img/WechatIMG36.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bz51/AntColonyAlgorithm/ab36ba1d745ae4d9cf8c5984fa4d2d07df6db55d/img/WechatIMG36.jpeg -------------------------------------------------------------------------------- /img/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bz51/AntColonyAlgorithm/ab36ba1d745ae4d9cf8c5984fa4d2d07df6db55d/img/result.png -------------------------------------------------------------------------------- /img/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bz51/AntColonyAlgorithm/ab36ba1d745ae4d9cf8c5984fa4d2d07df6db55d/img/wechat.png --------------------------------------------------------------------------------