0 前言

因个人之前遇到过多目标货位分配问题,采用的是NSGAⅡ算法进行求解,在找资料时发现代码大多用的matlab或者python,用C++的比较少,而当时自己用的是C++语言,参考了Github上面的一些代码,算是大体搞定,现在贴出来方便后续自己查看,也供大家参考和交流

1 算法流程

多目标货位分配问题是以货架稳定性、库区分配优化以及物料搬运距离作为优化目标,在获取现在库存信息的基础上做出货位分配决策——即为每个入库物料分配最佳存储货位;整体算法流程如下所示,当满足最大迭代次数时,得到是一个最优pareto解集,如何从这些解集中选出一个作为最优解,根据自己所看文献来看,有的是采用加权求和得出,也有的是结合问题背景实际需求直接选取一个作为最优解,这里为了简化说明,最优解采用随机选择

货位分配python 货位分配优化算法_i++

2 代码

2.1 代码文件

由于算法中涉及的参数众多,建了一个Parameters.h文件来进行统一存储,Read_csv.h文件用于读写存储在excel中的库存信息

货位分配python 货位分配优化算法_#define_02

2.2 辅助文件——Read和Parameters

1、Read.h文件
关于Read.cpp文件这里就不贴出来了,excel读写在另一篇文章有提及,这里贴出.h文件只是为了方便后面代码中出现的一些关于read类中成员变量和函数的理解

#pragma once
#include<vector>
using namespace std;

class Read
{
public:
	vector<vector<double>> Material_WFV; //物料的重量、频次、存储该类货品总量的库存容量
	Read();//构造函数
	void Read_Warehouse();
	vector<vector<vector<int>>> Store_1;//一楼仓库货位状态
	vector<vector<int>> Store_2;//二楼仓库货位状态
	vector<vector<int>> s1_index;//存储一楼仓库货位为空的坐标
	vector<vector<int>> s2_index;//存储二楼仓库货位为空的坐标
	void Get_store_infro();
	int num_1;//获取当前可用货位数
	int num_2;//获取当前可用货位数
};

2、Parameters.h文件
参数主要分为两部分,一部分是算法参数,另一部分就是仓库参数

#pragma once
//仓库参数
#define W1_rows 10 //一楼仓库排数
#define W2_rows 10 
#define W1_cols 6  //一楼仓库列数
#define W2_cols 4
#define W1_floors 4 //一楼仓库层数
#define Weight 150  //临界重量
#define W1_x 5.5 //一楼仓库出入库坐标
#define W1_y -1.5
#define W2_x 3.5 //二楼仓库出入库坐标
#define W2_y -1
#define Floor_h 4  //一楼楼层高度
#define W1_rd 1.5 //一楼仓库排距
#define W2_rd 2
#define W1_cd 0.8 //一楼仓库列距
#define W2_cd 1.2
#define W1_fh 0.5 //一楼仓库层高
#define W2_fh 2


//算法参数
#define GENE_SIZE 10 //基因大小,即物料数量
#define popSize 30
#define Num_F 3
#define Crossover_prob 0.6
#define Mutation_prob 0.01
#define Generation 100

2.3 Individual.h文件和Individual.cpp文件

1、Individual.h文件
将目标函数值、支配个体数量及集合、排序等级以及拥挤度距离作为个体属性,个体成员函数是要是初始化、计算个体目标函数值以及支配关系判断

#pragma once
#ifndef INDIVIDUAL_H
#define INDIVIDUAL_H
#include<vector>
using namespace std;

class Individual
{
public:
	Individual();
	Individual(int);
	void Cal_FV();//计算目标函数值
	bool dominate(Individual);//判断支配关系

	vector<vector<int>> Gene;
	vector<double> fValue;//目标函数值
	int np;//支配个体数量
	vector<Individual*>Sp;//支配个体集合
	int rank; //个体排序等级
	double distance;//个体拥挤距离
};

#endif

2、Individual.cpp文件

#include "Individual.h"
#include "Parameters.h"
#include"Read_csv.h"
#include <string>  
#include <vector>  
#include<cmath>
#include <vector> 
#include<algorithm>
using namespace std;

Individual::Individual()
{

}

//这里采用有参构造进行初始化个体,参数a就是物料的数量
//个体基因编码为四层,一般货位分配时采用三层编码表示排列层,但是因为个人分析对象是个两层仓库,所有设置四层,第一层用1/2表示第一层仓库和第二层仓库,物料的重量将决定应呗放置在第一层还是第二层,林茨在Parameters文件中有个临界重量值
Individual::Individual(int a)
{
	Read R;
	
	R.Read_Warehouse();//读取货位信息
	R.Get_store_infro();//获取当前可用货位索引并记录
	vector<int>temp(a);//a为基因个数
	Gene.resize(4, temp);//染色体初始化
	random_shuffle(R.s1_index.begin(), R.s1_index.end());//随机排列
	random_shuffle(R.s2_index.begin(), R.s2_index.end());
	
	//打乱原本一二楼仓库中的可用货位记录排序,然后按照打乱后顺序依次取作为分配的货位,直到基因个数循环为止,以此达到随机编码且使编码有效的目的

	int temp1=0, temp2 = 0;
	for (int i = 0; i < a; i++)
	{	
		if (R.Material_WFV[i][0] > Weight)//二楼仓库
		{
			Gene[0][i] = 2;	
			Gene[1][i] = R.s2_index[temp1][0];
			Gene[2][i] = R.s2_index[temp1][1];
			Gene[3][i] = 1;
			temp1++;
		}
		else//一楼仓库
		{
			Gene[0][i] = 1;	
			Gene[1][i] = R.s1_index[temp2][0];
			Gene[2][i] = R.s1_index[temp2][1];
			Gene[3][i] = R.s1_index[temp2][2];
			temp2++;
		}

	}

	//测试——输出基因
	/*for (int i = 0; i <Gene.size(); i++)
	{
		for (int j = 0; j < Gene[i].size(); j++)
		{
			cout << Gene[i][j] << " ";

		}
		cout << endl;
	}
	cout << "-------------" << endl;*/
}

//目标函数有三个,具体数学公式就不贴了
void Individual::Cal_FV()
{
	Read R;
	fValue.resize(3);
	double Tol_W = 0;//局部变量
	double Tol_G = 0;
	for (int i = 0; i <GENE_SIZE; i++)
	{
		//计算f1=物料搬运距离
		double dis;
		if (Gene[0][i] == 1)
		{
			dis = abs(Gene[1][i] - W1_x) * W1_rd + abs(Gene[2][i] - W1_y) * W1_cd + Gene[3][i] * W1_fh;
		}
		else
		{
			dis = abs(Gene[1][i] - W2_x) * W2_rd + abs(Gene[2][i] - W2_y) * W2_cd + Floor_h;
		}
		fValue[0] += dis;

		//计算f2货架稳定性 (重心*总重量)/总重量
		double tmpG = 0;
		double tmpW = 0;
		if (Gene[0][i] == 1)
		{
			tmpW = R.Material_WFV[i][0];
			tmpG = tmpW * Gene[3][i] * W1_fh;

		}
		Tol_G += tmpG;
		//cout << "Tol_G="<<Tol_G << endl;
		Tol_W += tmpW;
		//cout << "Tol_W="<<Tol_W << endl;
		

		//计算f3库区分配优化——使频次较高的货品尽可能存在距离仓库入料口最近的货位
		double tmp_fV2;
		double I_k = R.Material_WFV[i][2] / R.Material_WFV[i][1];
		tmp_fV2 = I_k * Gene[0][i] * Gene[1][i] * Gene[2][i] * Gene[3][i];
		fValue[2] += tmp_fV2;

	}
	fValue[1] = Tol_G / Tol_W;
}

bool Individual::dominate(Individual q)
{

	for (int i = 0; i < fValue.size(); i++)
	{
		if (this->fValue[i] > q.fValue[i])
		{
			return false;
		}
	}
	return true;
}

2.4 Population.h文件和Population.cpp文件

1、Population.h文件
在构建了个体文件后,现在开始构建群体文件

#pragma once
#include<vector>
#include "Individual.h"

#ifndef POPULATION_H
#define POPULATION_H
using namespace std;

typedef vector<Individual*> Front;//非支配个体集合

class Population
{
public:
	Population();//初始化、计算群体目标值
	Population(const Population& p);
	vector<Front> Fast_non_Domination_sort();//快速非支配排序
	void cal_crowing_distance(Front);//计算拥挤距离
	void Selection();//选择操作
	void Crossover();//交叉操作
	void Mutation();//变异操作
	void evaluation();
	
	vector<Individual> individualSet;
	int index;//选择交叉的id
	
};
#endif

2、Population.cpp文件
这一部分实现算法的主体内容,包括非支配排序、拥挤度计算、选择、交叉以及变异等内容

#include "Population.h"
#include "Parameters.h"
#include <algorithm> 
#include <deque> 
#include <limits>
#include <random> 
#include <time.h> 

Population::Population()
{
	for (int i = 0; i < popSize; i++)
	{
		individualSet.push_back(Individual(GENE_SIZE));
	}

}

//此处用有参构造实现子代的第一次获得,即复制父代,随后再通过交叉变异获得真正的子代
Population::Population(const Population& p)
{
	this->individualSet = p.individualSet;
}

//计算群体
void Population::evaluation()
{
	for (int i = 0; i < popSize; i++)
	{
		this->individualSet[i].Cal_FV();
	}
}

//快速非支配排序
vector<Front> Population::Fast_non_Domination_sort()
{
	vector<Front> front;//非支配个体集合
	front.resize(1);
	
	for (int i = 0; i < popSize; i++)
	{
		Individual* p = &this->individualSet[i];
		p->Sp.clear();
		p->np = 0;//初始化支配个体集合和支配个体数量

		for (int j = 0; j < popSize; j++)
		{
			if (i != j)
			{
				Individual* q = &this->individualSet[j];
				if (p->dominate(*q))
				{
					p->Sp.push_back(q);
				}
				else if (q->dominate(*p))
				//else
				{
					p->np++;
				}
			}
		}
		if (p->np == 0)
		{
			p->rank = 0;
			front[0].push_back(p);//得到当前集合F1
		}
	}

	int i = 0;
	while (front[i].size() != 0)
	{
		vector<Individual*> tmpSet;
		tmpSet.resize(0);
		for (int j = 0; j < front[i].size(); j++)
		{
			Individual* p = front[i][j];//非支配个体
			for (int k = 0; k < p->Sp.size(); k++)//遍历支配个体集合
			{
				Individual* q = p->Sp[k];
				q->np--;
				if (q->np == 0)
				{
					q->rank = i + 1;
					tmpSet.push_back(q);//得到集合H
				}
			}

		}
		i++;
		front.push_back(tmpSet);

	}
	return front;//直至整个种群全部分级返回
}

//计算拥挤度距离
bool comparator_0(Individual* a, Individual* b)
{
	return a->fValue[0] < b->fValue[0];
}

bool comparator_1(Individual* a, Individual* b)
{
	return a->fValue[1] < b->fValue[1];
}

bool comparator_2(Individual* a, Individual* b)
{
	return a->fValue[2] < b->fValue[2];
}

void Population::cal_crowing_distance(Front f)
{
	int l = f.size();//返回的是非支配解集某一层的数量大小
	//存储每个目标函数值,利用deque属性获得最大值以及最小值
	deque<double> l1, l2, l3;

	for (int i = 0; i < popSize; i++)
	{
		l1.push_back(this->individualSet[i].fValue[0]);//存储目标函数1的值
		l2.push_back(this->individualSet[i].fValue[1]);
		l3.push_back(this->individualSet[i].fValue[2]);

	}
	vector<double>max; max.resize(3);
	vector<double>min; min.resize(3);
	max[0] = l1.back(); max[1] = l2.back(); max[2] = l3.back();
	min[0] = l1.front(); min[1] = l2.front(); min[2] = l3.front();

	//初始化同层个体初始化距离
	for (int i = 0; i < l; i++)
	{
		f[i]->distance = 0;
	}
	//排序
	for (int m = 0; m < Num_F; m++)
	{
		if (m == 0)
		{
			sort(f.begin(), f.end(), comparator_0);//升序排列
		}
		if (m == 1)
		{
			sort(f.begin(), f.end(), comparator_1);
		}
		if (m == 2)
		{
			sort(f.begin(), f.end(), comparator_2);
		}

		f[0]->distance = numeric_limits<double>::infinity();//两边定义无穷大
		f[l - 1]->distance = numeric_limits<double>::infinity();

		for (int j = 2; j < l - 1; j++)
		{
			f[j]->distance = f[j]->distance + (f[j + 1]->fValue[m] - f[j - 1]->fValue[m]) / (max[m] - min[m]);
		}
	}
	
}

//选择
void Population::Selection()
{
	vector<int> I_d;
	this->index = 0;
	for (int i = 0; i < popSize; i++)
	{
		I_d.push_back(i);
	}

	random_shuffle(I_d.begin(), I_d.end());
	int ind1 = I_d[0];
	int ind2 = I_d[1];//随机选择两个个体
	
	//先比较非劣级别rank,若rank相同,比较其拥挤距离
	if (individualSet[ind1].rank > individualSet[ind2].rank)
	{
		this->index = ind1;
	}
	else if (individualSet[ind1].rank < individualSet[ind2].rank)
	{
		this->index = ind2;
	}
	else
	{
		int dis1 = individualSet[ind1].distance;
		int dis2 = individualSet[ind2].distance;
		if (dis1 >= dis2)
		{
			this->index = ind1;
		}
		else
		{
			this->index = ind1;
		}

	}
}


//交叉
void Population :: Crossover()
{
	vector<int> v;
	for (int i = 0; i < GENE_SIZE; i++)
	{
		v.push_back(i);
	}
	
	for (int i = 0; i < popSize; i++)
	{
		random_device rd;
		mt19937 rng(rd());
		uniform_real_distribution<> dist_real(0.0, 1.0);//随机生成一个0-1之间的值

		vector<int>temp(GENE_SIZE);
		vector<vector<int>> tmp_Gene;
		tmp_Gene.resize(4, temp);//临时变量——存储交叉的基因段
		if (dist_real(rng) <Crossover_prob)
		{
		//	cout << "概率="<< prob <<endl;
			random_shuffle(v.begin(), v.end());//随机排列
			
			int c1 = v[0];
			int c2 = v[1];//按照随机顺序选取前两个作为交叉点

			//cout << c1 << endl;
			//cout << c2 << endl;
			if (c1 > c2)
			{
				int tmp = c1;
				c1 = c2;
				c2 = tmp;
			}//比较大小

			for (int k = 0; k < 4; k++)
			{

				for (int j = c1; j <=c2; j++)
				{
					tmp_Gene[k][j] = individualSet[i].Gene[k][j];
					this->Selection();
					int ind = this->index;//通过选择确定交叉的染色体
					individualSet[i].Gene[k][j] = individualSet[ind].Gene[k][j];
					individualSet[ind].Gene[k][j] = tmp_Gene[k][j];

				}

			}
			tmp_Gene.clear();
			
		}
		
	}
}

//变异
void Population::Mutation()
{
	vector<int> v;
	for (int i = 0; i < GENE_SIZE; i++)
	{
		v.push_back(i);
	}
	
	for (int i = 0; i < popSize; i++)
	{
		random_device rd;
		mt19937 rng(rd());
		uniform_real_distribution<> dist_real(0, 1);//随机生成0-1数

		vector<int> tmp(1);
		vector<vector<int>> tmp_Gene;
		tmp_Gene.resize(4,tmp);
		float m = dist_real(rng);
		//cout << "随机概率" << m << endl;
		if (m < Mutation_prob)
		{
			random_shuffle(v.begin(), v.end());
			int c1 = v[0];
			int c2 = v[1];//随机生成要交换的的两个基因
			//由于有两个仓库,因此只有属于同一仓库的物料基因进行变异才算有效变异,细齿需要判断是否属于同以仓库
			if (individualSet[i].Gene[0][c1] == individualSet[i].Gene[0][c2])
			{
				for (int j = 0; j < 4; j++)
				{
					tmp_Gene[j][0] = individualSet[i].Gene[j][c1];
					individualSet[i].Gene[j][c1] = individualSet[i].Gene[j][c2];
					individualSet[i].Gene[j][c2] = tmp_Gene[j][0];

				}
			}
			else//若不是同一仓库,则重新选择直至为放置在同一仓库的物料为止
			{
				for (int k = 2; k < v.size(); k++)
				{
					int c3 = v[k];
					if (individualSet[i].Gene[0][c3] == individualSet[i].Gene[0][c1])
					{
						for (int j = 0; j < 4; j++)
						{
							tmp_Gene[j][0] = individualSet[i].Gene[j][c1];
							individualSet[i].Gene[j][c1] = individualSet[i].Gene[j][c2];
							individualSet[i].Gene[j][c2] = tmp_Gene[j][0];
							break;

						}
					}
					else if (individualSet[i].Gene[0][c3] == individualSet[i].Gene[0][c2])
					{
						for (int j = 0; j < 4; j++)
						{
							tmp_Gene[j][0] = individualSet[i].Gene[j][c1];
							individualSet[i].Gene[j][c1] = individualSet[i].Gene[j][c2];
							individualSet[i].Gene[j][c2] = tmp_Gene[j][0];
							break;
						}
					}
				}
			}			

		}
	}

}

2.5 Main.cpp文件

这部分根据上面的算法流程逻辑顺序进行编写,最后得到相应的解集,关于流程图中的库存信息更新,就是根据选择的一个解对原本货位的存储状态进行更改,这样下一次问题求解时可以保证已被分配的货位不会再被重新分配,也是与excel的读写更新有关,此处不再赘述

#include <iostream>
#include "Individual.h"
#include "Population.h"
#include "Parameters.h"
#include <vector> 
#include <ctime> 
#include <cmath> 
#include <random>    
#include <algorithm>  
#include"Read_csv.h"
#include <map>
#include <stdio.h>
#include <stdlib.h>

using namespace std;
Population Combination(Population& p1,Population&p2)
{
	Population tmp;
	tmp.individualSet.clear();
	for (int i = 0; i < popSize; i++)
	{
		tmp.individualSet.push_back(p1.individualSet[i]);
	}
	for (int i = 0; i < popSize; i++)
	{
		tmp.individualSet.push_back(p2.individualSet[i]);
	}
	return tmp;
}


bool Descending(Individual* a, Individual* b)
{
	if ((a->rank < b->rank) || ((a->rank == b->rank) && (a->distance > b->distance)))
	{
		return true;
	}
	else
	{
		return false;
	}

}


int main()
{
	srand(int(time(0)));//随机种子
	Population Parent;
	Parent.evaluation();
		for (int i = 0; i < Generation; i++)
	{
		Population Child(Parent);
		Child.Crossover();
		Child.Mutation();
		Child.evaluation();

		Population tmp_Comb = Combination(Parent,Child);//结合父代与子代群体
		vector<Front> F = tmp_Comb.Fast_non_Domination_sort();//F为非支配解集
		Parent.individualSet.clear();//清空以便存储新的父种群

		int j = 0;
		while (Parent.individualSet.size() + F[j].size() <= popSize)
		{
			if (F[j].size() == 0)
			{
				//cin.get();
				break;
			}
			tmp_Comb.cal_crowing_distance(F[j]);
			for (Individual* ind : F[j])
			{
				Parent.individualSet.push_back(*ind);
			}
			j++;
		}
		sort(F[j].begin(), F[j].end(), Descending);
		int tmp_Size = Parent.individualSet.size();
		for (int k = 0; k < (popSize - tmp_Size); k++)
		{
			Parent.individualSet.push_back(*F[j][k]);
		}

	}
//将结果写入文件
	ofstream os("Record_res.txt");
	if (os.is_open())
	{
		os << "NSGA2" << endl;

		for (int i = 0; i < popSize; i++)
		{
			os<< "第" << i << "个个体" << endl;
			for (int j = 0; j < Parent.individualSet[i].Gene.size(); j++)
			{
				for (int k = 0; k < Parent.individualSet[i].Gene[j].size(); k++)
				{
					os << Parent.individualSet[i].Gene[j][k] << " ";
				}
				os << endl;
			}
			//os << endl;
			os << "第" << i << "个个体目标函数值" << endl;
			os << "搬运距离:" << Parent.individualSet[i].fValue[0] << "   货架稳定性:" << Parent.individualSet[i].fValue[1] << "   库区分配优化度:" << Parent.individualSet[i].fValue[2] << endl;
			os << endl;
		}
		os << endl;
		
	}
	os.close();
	
}