# 遗传算法解决矩形块排样问题——JS实现
遗传算法作为一种启发式算法,对于寻优问题是一种很好的解题思路,求解精度当然是不如数学建模好,但是也能解决矩形块的排布寻优问题。
## 问题描述
原料高度H=20,宽度W=30,需要下料以下零件{(hi, wi) : i ∈ {1, . . . , 5}} ={(5, 7), (5, 10), (7, 12), (10, 8), (12, 10)},各自的需求量为b={4,3,5,3,5},要求最小化所需的原料块数。(单位:cm)
## 解决思路
按照遗传算法,其流程如下:
下面一个一个流程来解释。
1. **编码**:遗传算法的编码规则有二进制和十进制等,按照算例中的所给出的要求,可按十进制编码规则来进行编码。
如:13,7,12,19,18,4,6,16,8,15,17,0,9,11,3,10,14,1,2,5。这样编码是根据算例中小矩形块的需求总量为20,因此将每一块小矩形块由0到19以次编号,该数字顺序代表小矩形块排放的先后顺序,该数字顺序的值代表小矩形块的编号。
2. **初始化种群**:按照编码规则生成的一个数字顺序代表一条染色体,即一个个体,由totalNum个个体组合而形成一个种群,种群大小可自行设定 。
3. **适应度函数** :适应度函数就相当于生物在遗传进化中的物竞天择、优胜劣汰。适应度越高表示有更大的机率存活下来。根据本题的实际情况,在计算适应度函数之前,首先要规定小矩形块在毛坯件内的排布规则。
(水平为X轴,竖直为Y轴)如图小矩形块在毛坯件内一行一行地往下排,直到余料不足以满足任何一块小矩形块的尺寸时,则再拿一块毛坯进行排列。在排一行的时候,按照染色体的排布顺序,从左往右排。当排到该行的剩余X尺寸不足以满足下一个即将排入的小矩形块的尺寸时,这时会在染色体的未进行排布的小矩形块里按顺序去搜寻其长或宽满足该行剩余X尺寸的小矩形块,如有满足该条件的小矩形块,则将该矩形块的编号在染色体的提前到当前排布顺序位置,其他所有矩形块的顺序不变;若找不到,则另起一行进行排列。
在排完一行时,需要找到该行排列的小矩形块的最大Y值。在排完所有小矩形块后,将每行的最大Y值累计,即作为该条染色体的适应度的值,并求适应度的最小值。
因为适应度值是累计得来的,因此可以根据适应度的值来计算所需要的毛坯数量neededNum。本题求需求量最小,因此在传入neededNum时需要传入其倒数,作为最终的适应度值。
5. **选择**:适应度函数值越高,则有更大的概率被选中而传入下一代。可以采用轮盘赌策略。
7. **交叉** :在一定的概率内会发生交叉现象,即染色体的某个基因片段进行互换。由于采用十进制编码,在交叉发生后,要判断该条染色体中是否存在重复基因,若重复,要将其找出来并加以修正。
从第6位基因发生交叉,如图会产生违规染色体,要将基因修正。![在这里插入图片描述](https://img-
8. **变异**:变异也是在一定的概率内才会发生。由于采用十进制编码,可直接选择某两位基因交换顺序即可。
10. **执行** 按照上述流程,以此执行即可。迭代次数大小可自行设定。
完整代码如下:
<!DOCTYPE >
<html>
<head>
<meta charset="utf-8"/>
<title></title>
</head>
<body>
<button type="button" id="button">计算结果</button>
<script type="text/javascript">
var totalNum = 50, // 种群中染色体的总数
width=20,//毛坯的宽
length=30,//毛坯的长
N,//记录需要的毛坯数量
bit = 20, // 基因数为20位
total = new Array(); // 种群中的染色体
bestFitness = 0, // 最佳适应值
generation = 0, // 染色体代号
bestGeneration = 0, // 最好的一旦染色体代号
bestStr = ''; // 最好的染色体基因序列
var size=[];//记录小矩形块的长宽
var num=[4,3,5,3,5];//记录小矩形块的需求数量
size[0]=[7,5];
size[1]=[10,5];
size[2]=[12,7];
size[3]=[10,8];
size[4]=[12,10];
var sizeTotal=[];
/*将基因组对应的小矩形块的长宽记录下来*/
for(var i=0;i<4;i++){
sizeTotal[i]=size[0];
}
for(var i=4;i<7;i++){
sizeTotal[i]=size[1];
}
for(var i=7;i<12;i++){
sizeTotal[i]=size[2];
}
for(var i=12;i<15;i++){
sizeTotal[i]=size[3];
}
for(var i=15;i<20;i++){
sizeTotal[i]=size[4];
}
/*初始化一条染色体*/
function initChar() {
var arr = [];
for(var i=0;i<bit;i++){
getx(arr);
}
function getx(arr){
for(var i=0;i>-1;i++){
var flag = true;
var num = Math.floor(Math.random()*20);
for(var i in arr){
if(arr[i] == num){
flag= false;
break;
}
}
if(flag == true){
arr.push(num);
return;
}
}
}return arr;
}
/*初始化一个种群*/
function initTotal() {
for(var i=0;i<totalNum;i++){
total[i] = initChar()
}
return total;
}
/*找出数组中的最大值*/
function findMax(tmp){
var max = tmp[0];
for(var i=1;i<tmp.length;i++){
if(max<=tmp[i]){
max=tmp[i];
}
}return max;
}
/*找出数组中的最小值*/
function findMin(tmp){
var min=tmp[0];
var index=0;
for(var i=1;i<tmp.length;i++){
if(min>tmp[i]){
min=tmp[i];
index=i;
}
}
return min;
}
/*找出数组中最小值的index*/
function findMaxIndex(tmp){
var max=tmp[0];
var index=0;
for(var i=1;i<tmp.length;i++){
if(max<tmp[i]){
max=tmp[i];
index=i;
}
}
return index;
}
/*计算适应度函数(也是直接计算需要的矩形块数量)*/
function calculateFitness(arr) {
var neededNum=1;
var newArr=[];//记录每一行的矩形块的宽
var addLenght=0;//累计长
var remLength=0;//剩余长
var fitness=0;
var n=0;//累计在出现addLenght>length的情况时i循环的次数的个数
var neededNum=0;
var index1,index2;
var count=0;//记录是否出现每行的remLenth还能再放一个矩形块的情况
for(var i=0;i<arr.length;i++){
addLenght+=sizeTotal[arr[i]][0];
n++;
if(addLenght>length){
if(i<n){
for(var j=0;j<i;j++){
newArr[j]=sizeTotal[arr[j]][1];
}
}else{
for(var j=0;j<i;j++){
newArr[j]=sizeTotal[arr[j]][1];
}
for(var a=0;a<i-n;a++){
newArr.shift();
}
}
remLength=length+sizeTotal[arr[i]][0]-addLenght;
if(remLength>5){
outerMost://定义外层循环
for(var m=i;m<arr.length;m++){
for(var h=0;h<2;h++){
if( sizeTotal[arr[m]][h] <= remLength){
index1=m;
index2=h;
count=1;
if(index2==0){
newArr.push(sizeTotal[arr[index1]][1]);
}else{
newArr.push(sizeTotal[arr[index1]][0]);
}
if(index1!=i){
arr.splice(i,0,arr[index1]);
arr.splice(index1+1,1);
}
if(i<arr.length-1){
i++;
}
break outerMost;//终止外层循环
}
}
}
}
fitness+=findMax(newArr);
addLenght=sizeTotal[arr[i]][0];
n=0;
}
var newArrRest=[];//储存排在最后一行的矩形块的宽
if(i==arr.length-1){
if(count==0){
for(var z=0;z<arr.length;z++){
newArrRest[z]=sizeTotal[arr[z]][1];
}
for(var b=0;b<i-n;b++){
newArrRest.shift();
}
fitness+=findMax(newArrRest);
}else{
for(var z=0;z<arr.length;z++){
newArrRest[z]=sizeTotal[arr[z]][1];
}
for(var b=0;b<i-n-1;b++){
newArrRest.shift();
}
fitness+=findMax(newArrRest);
}
}
if(fitness>width){
neededNum++;
fitness=findMax(newArr);
}
count=0;
}
return 1/neededNum;
}
/*轮盘赌选择*/
function select() {
var evals = new Array(totalNum); // 所有染色体适应值
var p = new Array(totalNum); // 各染色体选择概率
var q = new Array(totalNum); // 累计概率
var F = 0; // 累计适应值总合
for(var i=0;i<totalNum;i++){ // 记录下种群的最优解
evals[i] = calculateFitness(total[i]);
if(evals[i] > bestFitness) {
bestFitness = evals[i];
bestStr = total[i];
}
F += evals[i];
}
for(var j=0;j<totalNum;j++){ // 计算累计概率
p[j] = evals[j]/F;
if(j == 0){
q[j] = p[j];
}
else{
q[j]=q[j-1]+p[j];
}
}
var temp = new Array(totalNum);
for(var k=0;k<totalNum;k++){ //
var r = Math.random();
if(r <= q[0]){
temp = total[0];
break;
}
else{
for(var z=1;z<totalNum;z++){
if(r<q[z]){
temp = total[z];
break;
}
}
}
}
return temp;
}
/*染色体交叉
*交叉概率为70%
*/
function jiaocha(population1,population2){
var returnPopulation1 = new Array(bit);
var returnPopulation2 = new Array(bit);
var temp=Math.random();
if(temp<0.7){
for(var i=0;i<bit;i++){
if(i<bit-2){
returnPopulation1[i]=population1[i];
returnPopulation2[i]=population2[i];
}else{
returnPopulation1[i]=population2[i];
returnPopulation2[i]=population1[i];
}
}
//找出重合的基因,并将相同基因改正
for(var j=bit-1;j>bit-3;j--){
for(var k=0;k<bit-2;k++){
if(returnPopulation1[k]==returnPopulation1[j]){
returnPopulation1[k]=returnPopulation2[j];
}
}
}
}else{
returnPopulation1=population1;
returnPopulation2=population2;
}
return returnPopulation1;
}
/*染色体变异*/
function change(arr) {
if(Math.random()<0.1){
while(1){
var temp1=parseInt(Math.random()*20);
var temp2=parseInt(Math.random()*20);
if(temp1!=temp2){
break;
}
}
var temp=arr[temp1];
arr[temp1]=arr[temp2];
arr[temp2]=temp;
}
return arr;
}
/*执行过程*/
var d1 = new Date();
document.getElementById('button').onclick = function () {
total = initTotal();
var arr=[];
var bestArr=[];
for(var i=0;i<1000;i++){
bestArr[i]=change(jiaocha(select(),select()));
arr[i] = calculateFitness(bestArr[i]);
}
//var minFitness=findMin(arr);
bestStr=bestArr[findMaxIndex(arr)];
//console.log(bestStr);
console.log(1/calculateFitness(bestStr));
var d2 = new Date();
//var time=d2-d1;
//console.log('一共执行了'+time+'ms');
}
</script>
</body>
</html>