文章目录
题目
- 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.
Capacitated Facility Location Problem
是一个 NP-hard
的问。 随机搜索算法是解决这类问题的一类比较好的解法。
在本篇博客中,使用 模拟退火(SA)算法 和 遗传算法(GA) 求解该问题。
完整代码
模拟退火(SA)算法
无论是模拟退火算法,还是遗传算法,都是在初始解的基础上通过相应的操作,得到较好的解的方法。
因为模拟退火算法与初始解无关,所以初始解可以随便给一个。
- 首先将顾客按顺序依次分配给各个设施,如果分配失败(分配后设备超出容量),则将顾客分配给下一个。
采用这样的分配方式,我们可以得到一个不确定好坏的初始解。
代码实现如下:
bool solutionGenerator() {
for (int i = 0; i < numOfCustomer; ++i) {
if (!assignCustomerToFicility(i, 0)) {
return false;
}
}
return true;
}
模拟退火算法的核心就是在初始解的基础上添加扰动产生新解,然后接受更优的解,对于差解,产生一个 [ 0 , 1 ) [0, 1) [0,1)的随机数 R R R,如果 R < e − Δ E / t R \lt e^{-\Delta E / t} R<e−ΔE/t , 则接受该解。
代码实现如下:
//初始温度为1000.0, 退火系数为 0.99
double t= 1000.0, a = 0.99;
int MapkobChainLength = numOfCustomer * numOfFacility;
int count = 0, lastCost = totalCost;
while (t > 0.01 && count < 20) {
for (int i = 0; i < MapkobChainLength; ++i) {
// 对解进行扰动
disturbance(t);
}
if (lastCost == solutionCost) {
count = count + 1;
} else {
count = 0;
}
t = a * t;
}
void disturbance(double t) {
int i = rand() % numOfCustomer;
int j = rand() % numOfFacility;
int temp = customerAssignedTable[i];
if (assignCustomerToFicility(i, j)) {
int dE = calculationCost() - solutionCost;
int k = customerAssignedTable[i];
if (dE <= 0 || rand() / (RAND_MAX + 1.0) < exp(-dE / t)) {
openingSolution[k] = actualCapacity[k];
openingSolution[temp] = actualCapacity[temp];
assignSolution[i] = customerAssignedTable[i];
solutionCost = totalCost;
} else {
actualCapacity[k] = openingSolution[k];
actualCapacity[temp] = openingSolution[temp];
customerAssignedTable[i] = assignSolution[i];
}
}
}
遗传算法(GA)
遗传算法的初始解和退火算法的初始解的产生方式大致相同,但是在分配顾客时采用的随机分配,产生100
个初始个体
bool initSolutionGenerator() {
bool isSuccess = true;
int count = 0, maxCount = 3;
for (int i = 0; i < MAX_INDIVIDUAL_NUM; ++i) {
isSuccess = true;
for (int j = 0; j < numOfCustomer; ++j) {
if (!assignCustomerToFicility(population[i], j, rand() % numOfFacility)) {
isSuccess = false;
break;
}
}
if (!isSuccess) {
--i;
count = count + 1;
} else {
count = 0;
calculationCost(population[i]);
}
if (count >= 3) {
return false;
}
}
return true;
}
采用锦标赛
的选择方式进行自然选择,然后交叉,变异。
void select() {
Individual temp[MAX_INDIVIDUAL_NUM];
memcpy(temp, population, sizeof(population));
for (int i = 0; i < MAX_INDIVIDUAL_NUM; ++i) {
int x = rand() % MAX_INDIVIDUAL_NUM;
int y = rand() % MAX_INDIVIDUAL_NUM;
x = population[x].totalCost < population[y].totalCost ? x : y;
memcpy(&population[i], &temp[x], sizeof(Individual));
}
}
void crossover() {
int x = rand() % (numOfCustomer - 1);
int y = rand() % (numOfCustomer - x - 1) + x + 1;
// 交叉概率
double Pc = 0.8;
for (int i = 0; i < MAX_INDIVIDUAL_NUM; i = i + 2) {
if (rand(