最近在實習中,遇到了一個實際問題。客戶要將若干大小不一的小矩形,排到大矩形上,而且還要求可以設置小矩形之間的間距,和大矩形的margin值,便於裁切。
排樣問題是一個經典的NP問題,有很多解決方案。神經網絡、遺傳、蟻群、模擬退火等等算法都可以解決這個問題。對於一些行業的工業生產,很多生產數據並沒有測試數據那般刁鑽,所以這些算法基本都能滿足生產的需要。
在這里,我主要參考了一篇鄭州大學的研究生畢業論文,自己又稍加了修改,用遺傳算法解決了這個問題。
遺傳算法的本質其實就是把問題簡化為一個個序列,根據一定規律隨機生成后,拿這個編碼序列貪心的得出解,然后不斷的迭代,優勝劣汰,向最優解靠攏。
我認為遺傳算法有幾個關鍵之處:編碼規則、初始種群的選取、貪心方式、適應度函數的選擇、變異規則。
1.好的編碼規則能便於程序的實現,同時也決定了程序的貪心結構,在這里,我的編碼是1-n的阿拉伯數字,代表放入的矩形的編號,從左到右是放入的順序。如果矩形需要旋轉,則為負數。
2.初始種群的選取,給矩形按照權值排序,權值=0.9*矩形面積+0.1*矩形長寬比,大的先放,小的后放,然后隨機其正負值,生成初始種群,好的初始種群能很快就找到最優解。
3.我的貪心解決方案就是最低水平線算法,從左到右,從下到上,不斷的尋找能放入的點。每次放入一個新點后,把未來的矩形可能的存在的點加入一個有序的序列中,后面的待排矩形就在這個序列里尋找可以放的點。
4.適應度函數,兩個個體好壞的決定因素,在這里,我設置了3個決定因素,首先是放入的矩形面積占待放入的矩形面積的百分比,第二個是當前排入的矩形的最大高度,第三個是排入矩形的整齊程度(高的種類個數)。我還有一些新想法,比如比較新圖形的重心,重心越靠左下約好,能避免一些相同適應值下的非最優解。
5.變異規則,這里用了4種變異方案,交叉、單點。。。。。。不詳述了。
這里我把算法封裝到了類里
#ifndef LAYOUTALGORYTHM_H
#define LAYOUTALGORYTHM_H
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <stack>
#include <QDebug>
using namespace std;
#include "layoutalgorythmglobal.h"
class LayoutAlgorythm;
struct MyPoint{
double x,y;
MyPoint(){}
MyPoint(double ix,double iy){
x=ix;
y=iy;
}
friend bool operator<(const MyPoint&a ,const MyPoint &b){
//按照y值排序,y相等時按照x排
if(a.y==b.y)return a.x<b.x;
else return a.y<b.y;
}
};
struct MyRect{
double width,height,S;
double ratio;
int index;
MyRect(){}
MyRect(double iWidth,double iHeight,int iIndex){
width=iWidth;
height=iHeight;
S=iWidth*iHeight;
ratio=max(height/width,width/height);
index=iIndex;
}
MyRect(double iWidth,double iHeight){
width=iWidth;
height=iHeight;
S=iWidth*iHeight;
ratio=max(height/width,width/height);
}
friend bool operator<(const MyRect &iRectA , const MyRect &iRectB){
double PriorityA=LayoutAlgorythmGlobal::m_weightArea*iRectA.S+LayoutAlgorythmGlobal::m_weightRatio*iRectA.ratio;
double PriorityB=LayoutAlgorythmGlobal::m_weightArea*iRectB.S+LayoutAlgorythmGlobal::m_weightRatio*iRectB.ratio;
return PriorityA>PriorityB;
//大的靠前
//Priority=WeightArea*R[i].S+WeightRatio*R[i].(L/W)
}
};
struct Individual
{
int m_order[100];//順序,有正負,沒有0
MyPoint m_position[100];//每個矩形的左下角位置
double m_val[3];//插入的面積,最高的水平線,獲得的高度種類個數
Individual(){}
friend bool operator<(const Individual&iIndiA ,const Individual &iIndiB){
if(iIndiA.m_val[0]==iIndiB.m_val[0]){
if(iIndiA.m_val[1]==iIndiB.m_val[1]){
return iIndiA.m_val[2]<iIndiB.m_val[2];//高度種類越小越好
}
else return iIndiA.m_val[1]<iIndiB.m_val[1];//最高水平線越小越好
}
else{
return iIndiA.m_val[0]>iIndiB.m_val[0];//插入的面積越大越好
}
}
};
class LayoutAlgorythm
{
public:
void setBigRect(const double iW,const double iH);
void setGap(const double iGap);
double getGap();
void setMargin(const double iMargin);
double getMargin();
void addRect(const double iW,const double iH,const int index);
void setRect(int position,MyRect iRect);
MyRect getRect(int position);
void dealWithGapMargin();
void solve();
void setRectsNum(int n);
int getRectsNum();
Individual getResult();
LayoutAlgorythm(){}
private:
//最低水平線算法,by Shijing
double m_totalS=0;
int m_num=0;//num_of_small_rect
Individual m_population[120];
MyRect m_rects[100];//第0個是container
double m_gap=0;//矩形之間的間距
double m_margin=0;//與邊框的間距
void initialize();//生成初始種群按照排序規則初始化后存到population里,並更新fitness值
int* randPerm(int N);//生成N個不同的1-N的隨機數
void fitness(Individual &myIndi);
void crossOne(const Individual iFirst,const Individual iSecond, Individual &oFirst, Individual &oSecond);//單點交叉
void crossTwo(const Individual iFirst,const Individual iSecond, Individual &oFirst, Individual &oSecond);//雙點交叉
void changePosition(const Individual input,Individual &output);//位置變異
void changeRotate(const Individual input,Individual &output);//旋轉變異
};
struct randnum
{
int index;
int v;
friend bool operator<(const randnum&a,const randnum&b){
return a.v<b.v;
}
};
#endif // LayoutAlgorythm_H
啊
#ifndef LAYOUTALGORYTHMGLOBAL_H
#define LAYOUTALGORYTHMGLOBAL_H
class LayoutAlgorythmGlobal
{
public:
static double m_weightArea;//矩形排序,面積權重
static double m_weightRatio;//矩形排序,長寬比權重
const static int m_members;//種群規模
const static int m_maxSize;//小矩形個數
static int m_iterCount;//最大迭代次數
static double m_pc1;//單點交叉概率
static double m_pc2;//兩點交叉概率
static double m_pm1;//兩點交換變異概率
static double m_pm2;//單點旋轉變異概率
LayoutAlgorythmGlobal();
};
#endif // LAYOUTALGORYTHMGLOBAL_H
后面,就是實現這些東西了
這里的solve,相當於主函數了,排序后留下的第一個,就是最優解了:
void LayoutAlgorythm::solve()
{
//隨機將前M個兩兩配對,產生后M個新個體,再排序,迭代50次
this->initialize();
int members=LayoutAlgorythmGlobal::m_members;
double Pc1=LayoutAlgorythmGlobal::m_pc1;
double Pc2=LayoutAlgorythmGlobal::m_pc2;
double Pm1=LayoutAlgorythmGlobal::m_pm1;
double Pm2=LayoutAlgorythmGlobal::m_pm2;
for(int i=0;i<LayoutAlgorythmGlobal::m_iterCount;i++){
int operationP=members;
for(int j=0;j<(int)(Pc1*members);j+=2){
crossOne(m_population[rand()%members],m_population[rand()%members],m_population[operationP],m_population[operationP+1]);
operationP+=2;
}
for(int j=0;j<(int)(Pc2*members);j+=2){
crossTwo(m_population[rand()%members],m_population[rand()%members],m_population[operationP],m_population[operationP+1]);
operationP+=2;
}
for(int j=0;j<(int)(Pm1*members);j++){
changePosition(m_population[rand()%members],m_population[operationP++]);
}
for(int j=0;j<(int)(Pm2*members);j++){
changeRotate(m_population[rand()%members],m_population[operationP++]);
}
sort(m_population,m_population+2*members);
}
}
原文鏈接:https://blog.csdn.net/qdbszsj/article/details/69236950