用贪心算法和模拟退货法解决CFLP问题
首先问题描述如下:
给定n个工厂和m个顾客,开工厂需要一定的费用,一个工厂有一定的容量限制,每个顾客也有一定的需求,而每个顾客要选取某个工厂也需要一定的分配费用,现在要求找出一个分配方案,把顾客分配给不同的工厂,然后在可以满足所有顾客需求的前提下让所有的花费(开工厂的花费和分配的花费)最小。
这显然是一个NP-hard问题,因为情况数非常的多,而且也没有什么固定的好的策略,所以现在我们就用贪心算法和模拟退火法两种方法来解决这个问题。
一、贪心算法
这个问题如果用贪心算法做的话思路应该是很简单的,就直接一个顾客一个顾客这样来选择工厂,然后在每次的选取过程中保证这个顾客的花费最小即可,但是这样做的话就会带来一个问题,就是开工厂的费用问题,如果把开工厂的费用都归到一个顾客的花费上的话,那么这个顾客要选取一个新工厂的花费就会很高,那么如果每个顾客采取这个策略就会都不愿意开新工厂,那么实际上就会去选取那些已经被其他顾客开的工厂。但是实际上开工厂的钱应该是要平摊到选取了这个工厂的所有顾客头上的,因此这样的策略显然不是太好。所以为了优化这个问题,我们可以在贪心的过程中直接不考虑开工厂的价钱(虽然这样做其实也没有符合很精确的开工厂的钱的平摊,但是因为在遍历过程中我们没办法确认后面的顾客会怎么选取工厂,所以只能退而求其次),然后在最后再看哪些工厂被选取了,然后再加上开这部分工厂的价钱即可,所以贪心部分的代码如下:
void CFLPSolver::beginGreedy()
{
//对每个顾客执行贪心策略
for(int i = 0; i < customerNum; i++)
{
int select = -1;
int curCost = 99999999;
//选取分配费用最小的工厂
for(int j = 0; j < facilityNum; j++)
{
if(demand[i] <= capacityLeft[j])
{
//int newCost = (1 - open[j]) * openCost[j] + allocateCost[j][i];
int newCost = allocateCost[j][i];
if(newCost < curCost)
{
select = j;
curCost = newCost;
}
}
}
//没有工厂可以选取了,说明这个样例的工厂的容量比较紧张,不能用简单的贪心策略做,不过在我测试的71个样例中都没有发现问题
if(select == -1)
{
cout << "greedy failed" << endl;
}
//选取工厂,更新工厂的剩余容量,开启被选取的工厂,加上分配的费用
else
{
allocate[i] = select;
capacityLeft[select] -= demand[i];
open[select] = 1;
finalCost += curCost;
}
}
//加上开工厂的费用
for(int i = 0; i < facilityNum; i++)
{
if(open[i])
{
finalCost += openCost[i];
}
}
}
而对样例的结果测试如下(这里只列出最终的cost和运行时间,具体的结果见文末的github连接):
instance | cost | time(ms) |
---|---|---|
p1 | 9440 | 9 |
p2 | 8126 | 7 |
p3 | 10126 | 10 |
p4 | 12126 | 6 |
p5 | 9375 | 15 |
p6 | 8061 | 0 |
p7 | 10061 | 16 |
p8 | 12061 | 0 |
p9 | 9040 | 15 |
p10 | 7726 | 16 |
p11 | 9726 | 0 |
p12 | 11726 | 21 |
p13 | 12032 | 14 |
p14 | 9180 | 3 |
p15 | 13180 | 16 |
p16 | 17180 | 15 |
p1 |