算法project报告
Capacitated Facility Location Problem
问题描述
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.
You need to obtain the results for 71 benchmark instances.
初始数据
一共给出了71个案例,需要找出每一种案例的解。
每个案例的数据格式如下:
J I //J个facility,I个customer.
//接下来J行给出了每个facility的capacity和opencost
s1 f1
s2 f2
…
sJ fJ//接下来个数据给出了每个customer的demand
d1 d2 d3 … dI
//接下来J * I个数据
//每I个数据对应I个customer分配给1个customer的assigncost
c11 c12 c13 … c1I
c21 c22 c23 … c2I
…
cJ1 cJ2 cJ3 … cJI
J is the number of potential facility locations;
I is the number of customers;
sj (j=1,…,J) is the capacity of facility j;
fj (j=1,…,J) is the fixed cost of opening facility j;
di (i=1,…,I) is the demand of customer i;
cji (j=1,…,J),(i=1,…,I) is the cost of allocating all the demand of customer i to facility j.customer的demand不可切分,即只能分配给一个falicity
问题分析
要求已经很明确,需要我们把I
个顾客分配到J
个设施去,每个设施初始时关闭,打开它需要opencost
,把顾客分配到设施需要assigncost
,设施的容量capacity
不能小于分配给它的顾客的需求demand
之和。我们需要求出一种分配方案,使得总的花费(打开设施的费用 + 分配顾客的费用)最小。
根据问题的需求及输入数据格式,我们可以把设施和顾客都抽象成结构体,属性如下:
1 | struct facility { |
1 | struct customer { |
利用循环读取数据就可以初始化所有的设施和顾客,然后我们需要求解:
- totalcost = opencost + assigncost
- 所有设施的打开状态open
- 每个顾客被分配到的设施
分别用一个int
变量和两个数组vector<bool> open
和vector<int> assign
即可存放结果。
我使用的两种算法是贪心算法+模拟退火算法。
项目代码github地址:https://github.com/chenf99/CFLP/tree/master/code
项目代码结构:
1 | ├── CFLP.cpp #主函数,主要处理输入输出及调用其他函数 |
贪心算法
思想
使用贪心算法的重点是确定每一步的选择策略,策略定下来后,贪心算法的最终解都是不会变的。
解的主要变化部分在于分配方式assign
,因此我考虑的主要是优化每一次分配方式,使得剩余容量多,且花费少。
我的贪心算法采取的策略是每次操作分配一个顾客到一个设施,并且每次进行的操作都是当前最优的操作,对于“最优”,我定义为性价比最高,即opencost + assigncost / demand
最小的操作,在每次分配后把对应设施的opencost
设为0,表示已经打开;并且在之后选取最优操作时,不会考虑已经被分配的顾客。
代码
1 | int greedy(vector<bool>& open, vector<int>& assign, vector<facility>& facilities, vector<customer>& customers) { |
模拟退火算法
使用贪心算法的话,很大程度上会局限于我们的策略,导致得不到比较好的解,而且对于不同的案例可能适合的策略也不同,因此我们最好还是使用启发式搜索来解决这种问题。
思想
模拟退火算法是在爬山法的基础上改进得来的,它避免了爬山法因为邻域没有更优解(陷入局部最优)而导致的问题,通过概率接受差解,能够跳出局部最优解,然后继续搜索寻找全局最优解。
模拟退火算法的基本步骤如下:
- 随机生成一个初始解,设置初温T、莫温T_end、每轮迭代次数count
- 迭代count次,对每次迭代进行如下操作:
- 产生新解
- 计算新解与原始解的评估值之差ΔE
- 如果ΔE < 0则新解更好,接受新解
- 否则以概率exp(-ΔE/T)接受差解
- 如果满足终止条件则得到最优解,终止程序
- 当前温度迭代次数完成,降温,一般降温方法为T = 0.99 * T
- 温度T <= T_end时结束程序
在本题中,解就是设施打开状态open
和分配方案assign
,解的评估值就是总的cost
产生新解的方法我采取了两种策略,每次迭代时随机选择一种:
- 随机选择两个
customer
,互相交换分配的facility
- 随机选择一个
customer
,把它随机分配到另外一个facility
本来我还采取了2-opt、插入策略和随机一次分配方式的策略,但感觉对效果没有提升,反而导致了产生好解的概率下降了,因此就抛弃了它们
此外,根据输入数据中后面复杂情况下opencost
很大的特点,我对产生初始解的方法进行了优化,即随机打开一个设施,给它分配顾客,直到容量已满为止,再去打开新的设施,直到所有顾客都被分配到。这样就可以减少很多opencost
,对于后面复杂的情况优化效果明显。
代码
1 | int SA(int i, vector<bool>& open, vector<int>& assign, vector<facility>& facilities, vector<customer>& customers) { |
结果
使用两种算法得到的结果如下所示(历史最优解见github)
贪心算法
1 | P1 result: |
SA算法
1 | P1 result: |
总结
可以从运行结果来比较这两种算法的效果:
- 贪心算法由于搜索次数比较少,因此耗时很少
- SA算法是启发式搜索,温度和每轮迭代次数都会影响它的速度和效果,我设计的SA算法耗时比贪心多了很多
- 这两种算法在前面比较简单的情况下结果相差不大,但在后面复杂的情况下,SA会不断寻找更优的解,并适时跳出局部最优解,因此SA算法得到的结果会好很多
- 通过比较设施的打开状态,我们可以发现,对于复杂的情况,贪心基本把所有设施都打开了,而SA则不会,节省了大量
opencost
,而输入数据实例告诉了我们opencost
是很昂贵的(1500一次),因此SA确实找到了更好的解,它考虑了打开设施的花费