题目描述
Suppose there are n facilities and m customers. We wish to choose:
(1) which of the n facilities to open
(2) 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.
解题思路
首先解析一下题目的意思。假设某地有n个可以开的工厂,m个顾客,每个工厂的开设都要不同的费用,而顾客为了满足自身的需求,到不同的工厂去消费也要不同的费用。现在需要找出,在满足了所有顾客的需求的情况下,最小的费用。这里要注意,每家工厂都有自己的容量,不可以无限制的满足顾客的需求,若一个工厂剩余的容量比某个顾客的需求要小,那么它是无法满足顾客的需求;而对于顾客而言,他只能去一家工厂满足自己的需求。
先来分析一下这里的约束条件。首先是工厂的开设费用,每个工厂的开设都是需要一定费用的;然后是顾客到工厂的花费,顾客为了满足自己的需求,需要找到容量足够的而且花费比较少的工厂;最后是工厂的容量,不能无限满足所有顾客的需求。这个问题的约束条件非常多,如果用传统的动态规划的思路来做,状态空间会非常大而且不好设计动态规划转移方程。因此,在这道题中我没有用到动态规划的思路。
贪心算法
如果动归很难设计的话,第一时间想到的就是贪心的算法。在满足约束的情况下找到最小解,贪心的思路就是:在满足每一个需求的时候都找一个最小的花费。设计起来也比较简单:假设所有工厂都是开放的,对于每一个顾客的需求,我们都找一个最小的,而且容量还足够的工厂。
FOR each customer:
loop: find the factory with the smallest cost
if the capacity of the factory smaller than the demand of the customer
goto loop
else
total cost += the cost of the customer going to the factory
FOR each factory:
if nobody going to the factory
total cost -= the opening cost
以上就是贪心算法的大概流程,我们需要遍历一次所有的顾客需求,然后找到最小的花费,这里可以用优先队列(heap)或者用排序算法从头开始找最好的工厂。由于题目保证了每个样例都有解,因此不需要特意考虑如果对于一个需求而言没有任何一个工厂能够有足够的容量来满足的情况。
尽管如此,贪心算法找到的solution并不是没有提升空间。这个算法只能解决customer到工厂最小花费的问题,但是没有考虑到开设工厂需要的花费,因此,从根本上来说,贪心算法达到的解不会是全局最优解。另外,由于现在是按照顺序对顾客进行遍历,前面的顾客可能会占据了后面的顾客的工厂容量,因此,达到的解也不一定是最优的。
为了让贪心算法尽可能逼近最优解,我们可以采用随机调换顾客顺序的方式,将得到的结果进行对比,得到相对较好的解。
模拟退火算法
前面提到了,贪心算法得到的解只是一个局部的最优解,并不能达到全局最优解。而这时,用模拟退火算法就可以有一定几率跳出局部最优解,到达全局最优解。
一开始,先随机一个解,这个解需要满足:
- 所有顾客都被满足
- 工厂还有剩余容量
然后,通过模拟退火算法:
T := INITIAL_TEMPERATURE
WHILE T > END_TEMPERATURE
for i from 1 to 1500:
generate new solution
if the cost of the new solution is smaller than the cost of the origin
accept the new solution
else
delta := new_cost - self.cost
probility := exp(-delta / T)
if probility > random
accept the new solution
T := T * 0.98
以上是模拟退火算法的大体流程。生成新解的方式(邻域搜索方式)是:(1)随机选取两个顾客交换它们的工厂(2)随机改变一个顾客的工厂。当然,生成的新解同样也需要满足原来的约束条件。
从结果可以看出,退火算法可以跳出局部最优解,但是,最后收敛的解也不一定是全局最优解,但值得肯定的是,退火算法得到的结果是比贪心的结果要优秀的。但不足的地方是,模拟退火算法的收敛需要很长的时间,而贪心算法则很快就能找到一个解。
结果分析
number | greed cost | sa cost |
---|---|---|
1 | 9440.0 | 8863.0 |
2 | 8126.0 | 7942.0 |
3 | 10126.0 | 9382.0 |
4 | 12126.0 | 10714.0 |
5 | 9375.0 | 8838.0 |
6 | 8061.0 | 7781.0 |
7 | 10061.0 | 9784.0 |
8 | 12061.0 | 11088.0 |
9 | 9040.0 | 8477.0 |
10 | 7726.0 | 7648.0 |
11 | 9726.0 | 8932.0 |
12 | 11726.0 | 10132.0 |
13 | 12032.0 | 8418.0 |
14 | 9180.0 | 7396.0 |
15 | 13180.0 | 9906.0 |
16 | 17180.0 | 11292.0 |
17 | 12032.0 | 8464.0 |
18 | 9180.0 | 7125.0 |
19 | 13180.0 | 8914.0 |
20 | 17180.0 | 11136.0 |
21 | 12032.0 | 8144.0 |
22 | 9180.0 | 7266.0 |
23 | 13180.0 | 8806.0 |
24 | 17180.0 | 11665.0 |
25 | 19197.0 | 12612.0 |
26 | 16131.0 | 11325.0 |
27 | 21531.0 | 13506.0 |
28 | 26931.0 | 15022.0 |
29 | 19305.0 | 13400.0 |
30 | 16239.0 | 11411.0 |
31 | 21639.0 | 14181.0 |
32 | 27039.0 | 16508.0 |
33 | 19055.0 | 12796.0 |
34 | 15989.0 | 11004.0 |
35 | 21389.0 | 13485.0 |
36 | 26789.0 | 15685.0 |
37 | 19055.0 | 12200.0 |
38 | 15989.0 | 11282.0 |
39 | 21389.0 | 12984.0 |
40 | 26789.0 | 14984.0 |
41 | 7226.0 | 6908.0 |
42 | 9957.0 | 6544.0 |
43 | 12448.0 | 6510.0 |
44 | 7585.0 | 7128.0 |
45 | 9848.0 | 6815.0 |
46 | 12639.0 | 6372.0 |
48 | 9044.0 | 6333.0 |
49 | 12420.0 | 5394.0 |
50 | 10062.0 | 9256.0 |
51 | 11351.0 | 7969.0 |
52 | 10364.0 | 9452.0 |
53 | 12470.0 | 9030.0 |
54 | 10351.0 | 9052.0 |
55 | 11970.0 | 8227.0 |
56 | 23882.0 | 22975.0 |
57 | 32882.0 | 29482.0 |
58 | 53882.0 | 44011.0 |
59 | 39121.0 | 33420.0 |
60 | 23882.0 | 22669.0 |
61 | 32882.0 | 29167.0 |
62 | 53882.0 | 42657.0 |
63 | 39121.0 | 32332.0 |
64 | 23882.0 | 22886.0 |
65 | 32882.0 | 29203.0 |
66 | 53882.0 | 44138.0 |
68 | 23882.0 | 22561.0 |
69 | 32882.0 | 29567.0 |
70 | 53882.0 | 43869.0 |
71 | 39121.0 | 33154.0 |
从这里我们可以很清晰的看出,退火算法得到的结果比贪心算法是有很大的提升的。