本篇博客涉及贪心算法以及模拟退火算法解决单源设施选址问题
问题描述
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.
数据集格式
此处AssignmentCost 有No.Customer行,No.Facility列。即每个用户对于不同的设备有不同的Assignment cost
思路分析
贪心算法
贪心算法是比较简单但不能够获得最优解的方法。使用贪心算法可以考虑较少的情况。我在贪心算法的编写中,忽略了对于用户分配顺序的考虑,直接从前到后进行分配。对于每个用户,首先通过用户的需求DEMAND来筛选出可用的设备,接着通过比较这些设备的opencost以及用户在这些设备上的assignment cost,选出其中的最小值。
显然,贪心算法得到的解并不是最好,并且可能导致后续用户没有可用分配
模拟退火算法
在本题中,我选择将所有用户的设备分配情况作为一个design,而总的opencost + assignment cost则作为目标方程。在生成新的Design时,一般情况下是选择当前解的领域解,但需要考虑这样的情况
因此,一直从后往前选择的话可能会变成下山法,所以我采用直接从前往后进行选择。即产生的解为随机解
代码实现
- 初始化
import random
import math
import matplotlib.pyplot as plt
CAPACITY = []
OPENCOST = []
ASSIGNCOST = []
OPENSTATUS = []
DEMAND = []
FACNUM = 0
CUSNUM = 0
Num = 0
#读取文件,获取各项变量
def init(filepath):
with open(filepath, 'r') as f:
global FACNUM
global CUSNUM
global CAPACITY
global OPENCOST
global ASSIGNCOST
global OPENSTATUS
FACNUM, CUSNUM = f.readline().strip("\n").split()
FACNUM = int(FACNUM)
CUSNUM = int(CUSNUM)
OPENSTATUS = [0] * FACNUM
for i in range(FACNUM):
line = f.readline().strip("\n").split()
CAPACITY.append(int(line[0]))
OPENCOST.append(int(line[1]))
for i in range(int(CUSNUM / 10)):
line = f.readline().strip("\n").replace(' ', '').split(".")
for j in range(10):
DEMAND.append(int(line[j]))
for i in range(CUSNUM):
linetoread = int(FACNUM / 10)
temp = []
for j in range(linetoread):
line = f.readline().strip("\n").replace(' ', '').split(".")
for k in range(10):
temp.append(int(line[k]))
ASSIGNCOST.append(temp)
print(ASSIGNCOST)
- 贪心算法
def greedy():
global CAPACITY
capacity = CAPACITY.copy()
openstatus = [0] * FACNUM
totalcost = 0
assign = [-1] * CUSNUM
assigncost = 0
opencost = 0
for cus in range(CUSNUM):
facIndex = [] # the facilities may be chosen
for i in range(FACNUM):
if capacity[i] >= DEMAND[cus]:
facIndex.append(i)
# ASSIGNCOST for customer i
asforeach = ASSIGNCOST[cus]
# ASSIGNCOST + OPENCOST for customer i
temp = [sum(x) for x in zip(asforeach, OPENCOST)]
# select the min cost index
sindex = facIndex[0]
for i in facIndex:
if temp[i] < temp[sindex]:
sindex = i
openstatus[sindex] = 1
capacity[sindex] = capacity[sindex] - DEMAND[cus]
assign[cus] = sindex
assigncost += asforeach[sindex]
for i in range(FACNUM):
opencost += openstatus[i] * OPENCOST[i]
totalcost = assigncost + opencost
print(assign)
print("assignment cost: %d"%assigncost)
print("opencost: %d"%opencost)
print(totalcost)
print("openstatus: {}".format(openstatus))
return assign
- 模拟退火
class Anneal:
assign = [] # list of Customer's choice , len = NumofCus
capacity = []
openstatus = []
totalcost = 0
assigncost = 0
opencost = 0
def __init__(self):
self.openstatus = [0] * FACNUM
self.assign = [-1] * CUSNUM
# 注意!!list的赋值是传引用,不是真正的copy。应该换为list.copy
self.capacity = CAPACITY.copy()
for i in range(CUSNUM):
available = []
for j in range(FACNUM):
if self.capacity[j] >= DEMAND[i]:
available.append(j)
choose = random.choice(available)
self.capacity[choose] = self.capacity[choose] - DEMAND[i]
self.assign[i] = choose
self.assigncost += ASSIGNCOST[i][self.assign[i]]
self.openstatus[self.assign[i]] = 1
for j in range(FACNUM):
self.opencost += self.openstatus[j] * OPENCOST[j]
self.totalcost = self.assigncost + self.opencost
def f(self):
return self.totalcost
def simulated_Annealing():
# Number of cycles
n = 50
# Number of trials per cycle
m = 50
# Number of accepted solutions
na = 0.0
# Probability of accepting worse solution at the start
p1 = 0.7
# Probability of accepting worse solution at the end
p50 = 0.001
# Initial temperature
t1 = -1.0 / math.log(p1)
# Final temperature
t50 = -1.0 / math.log(p50)
# Fractional reduction every cycle
frac = (t50 / t1) ** (1.0 / (n - 1.0))
# Initialize x
x = []
newassign = Anneal()
x.append(newassign)
xi = x[0]
na = na + 1.0
# Current best results so far
xc = x[0]
fc = xc.f()
fs = []
fs.append(fc)
# Current temperature
t = t1
# delta Average
delta_avg = 0.0
for i in range(n):
print('Cycle: ' + str(i) + ' with Temperature: ' + str(t))
for j in range(m):
# Generate new trial points
xi = Anneal()
delta = abs(xi.f() - fc)
if xi.f() > fc:
# Initialize delta_avg if a worse solution was found
# on the first iteration
if i == 0 and j == 0:
delta_avg = delta
# objective function is worse
# generate probability of acceptance
p = math.exp(-delta / (delta_avg * t))
# determine whether to accept worse point
if random.random() < p:
# accept the worse solution
accept = True
else:
# don't accept the worse solution
accept = False
else:
accept = True
if accept is True:
xc = xi
fc = xi.f()
# increment number of accepted solutions
na = na + 1.0
# update delta_avg
delta_avg = (delta_avg * (na - 1.0) + delta) / na
# Record the best x values at the end of every cycle
x.append(xc)
fs.append(fc)
# Lower the temperature for next cycle
t = frac * t
# print solution
print('Best solution: ' + str(xc.assign))
print('Best objective: ' + str(fc))
print('assign cost: %d' % xc.assigncost)
print('opencost: %d' % xc.opencost)
print('openstatus:{}'.format(xc.openstatus))
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(fs, 'r.-')
ax1.legend(['Objective'])
ax2 = fig.add_subplot(212)
ax2.plot(xc.assign, 'b.')
ax2.legend(['assignment'])
# Save the figure as a PNG
plt.savefig('iterations.png')
plt.show()
运行结果
-
贪心
-
模拟退火
-
所有测例结果
测例 | result | time | ||
---|---|---|---|---|
贪心 | 模拟退火 | 贪心 | 模拟退火 | |
p1 | 8498 | 19645 | 0.00076 | 2.209 |
p2 | 9041 | 19691 | 0.00059 | 1.232673 |
p3 | 11041 | 20656 | 0.00059 | 1.141639 |
p4 | 13041 | 23497 | 0.000398 | 1.189788 |
p5 | 13183 | 21249 | 0.00036 | 1.217148 |
p6 | 9513 | 18960 | 0.00036 | 1.195 |
p7 | 11513 | 22057 | 0.00036 | 1.179 |
p8 | 11432 | 21032 | 0.00045 | 1.167 |
p9 | 11245 | 21321 | 0.00045 | 1.232 |
p10 | 12113 | 21002 | 0.0033 | 1.214 |
p11 | 14097 | 17937 | 0.0002388887793277094 | 2.1204225189034434 |
p12 | 9718 | 21400 | 0.0008501598521910548 | 1.7513507015347138 |
p13 | 8267 | 18636 | 0.0007761457421708519 | 1.3833779247258877 |
p14 | 10779 | 19245 | 0.00025261508807137206 | 1.8370976323881316 |
p15 | 12259 | 22203 | 0.0003596301842028789 | 1.4753091504308617 |
p16 | 8337 | 23187 | 0.00024378627460451438 | 1.522405570563798 |
p17 | 8293 | 22673 | 0.0003703139043843042 | 1.683500707666125 |
p18 | 13726 | 21101 | 0.0008365639087288284 | 2.37975234090938 |
p19 | 8435 | 17830 | 0.0007232209484137225 | 1.7592657987186129 |
p20 | 11040 | 23192 | 0.0002757067721610368 | 2.246349242969258 |
p21 | 12700 | 19958 | 0.0005068108918336679 | 1.7403384749015314 |
p22 | 9979 | 21992 | 0.0001109954649953437 | 1.3276989768748344 |
p23 | 9647 | 18544 | 0.0005626644716672123 | 1.6609951905584563 |
p24 | 10639 | 17472 | 0.0007133010391008938 | 1.1590103877223343 |
p25 | 12707 | 22540 | 0.0005347218749303618 | 1.3111166520920643 |
p26 | 12423 | 19113 | 0.0005425276308653035 | 1.6612034356782832 |
p27 | 12590 | 22931 | 0.0004119945511482627 | 2.044352333196343 |
p28 | 11963 | 20755 | 0.00010553246875135694 | 1.793188627276194 |
p29 | 9789 | 23063 | 0.000728669534581603 | 1.4637117678613822 |
p30 | 10331 | 18546 | 0.0003385151801413741 | 1.9078472374750857 |
p31 | 9418 | 18394 | 0.00014250504393344672 | 2.067755491745352 |
p32 | 9167 | 22364 | 0.00032594163151644445 | 1.2634427849114496 |
p33 | 13952 | 17737 | 0.00047667496710894144 | 2.3784752175025874 |
p34 | 12102 | 19043 | 0.0002664715858721881 | 1.5072313865400668 |
p35 | 11184 | 19593 | 0.0004445802079924833 | 1.4873528144020982 |
p36 | 12351 | 16494 | 0.00024331209416919293 | 2.2408305779842292 |
p37 | 14014 | 20967 | 0.0007356799447518672 | 1.531007004413978 |
p38 | 13083 | 18789 | 0.0016665679956422218 | 2.7363278891088365 |
p39 | 12245 | 22108 | 0.0025178354490367613 | 2.22419974572203 |
p40 | 14020 | 22036 | 0.00287006026475386 | 2.570584215852868 |
p41 | 12913 | 20110 | 0.0022252554407345315 | 3.328531688044958 |
p42 | 12429 | 21013 | 0.002877983931398792 | 2.7221039224684214 |
p43 | 10553 | 21954 | 0.0020935759633520486 | 2.3940863372930363 |
p44 | 11393 | 18229 | 0.0018149032112489746 | 3.29131842441069 |
p45 | 12520 | 19987 | 0.001411162815252279 | 2.7629983863571765 |
p46 | 14666 | 18857 | 0.0020177059505604887 | 2.35614329460681 |
p47 | 11311 | 22473 | 0.0019542696251172538 | 2.54853823974901 |
p48 | 11847 | 21092 | 0.001281688229963093 | 2.9401093364080664 |
p49 | 12483 | 21570 | 0.0016020646271611451 | 2.469400858094551 |
p50 | 12041 | 20566 | 0.0013875142367434335 | 2.545814887398914 |
p51 | 14868 | 19644 | 0.001984131684255792 | 3.031923160336729 |
p52 | 11250 | 22197 | 0.002190513911085248 | 2.784567423266155 |
p53 | 19266 | 40815 | 0.0037283588827462053 | 4.734696141082202 |
p54 | 16538 | 37977 | 0.005091494117570511 | 4.962815956683496 |
p55 | 17090 | 30191 | 0.0049373375322601075 | 4.622363943876621 |
p56 | 20189 | 22027 | 0.004862308752880511 | 6.13632113300098 |
p57 | 16250 | 32534 | 0.004371748178831857 | 4.574235790074207 |
p58 | 19499 | 36915 | 0.0057753951282135925 | 4.8478838019477 |
p59 | 20624 | 33722 | 0.0036656781000750528 | 4.117314251202088 |
p60 | 19843 | 29409 | 0.0037484529098043584 | 5.5945888299197355 |
p61 | 16596 | 41807 | 0.005518804656960051 | 4.659352047542419 |
p62 | 18967 | 26603 | 0.004645578163212637 | 5.324560150699286 |
p63 | 17402 | 32011 | 0.004319263096421779 | 6.074201180149529 |
p64 | 20762 | 37708 | 0.003347568556529948 | 6.364104417141454 |
p65 | 17117 | 36609 | 0.0041139002438534905 | 4.248342713149869 |
p66 | 17755 | 24607 | 0.003946885862667265 | 5.359500050019501 |
p67 | 17999 | 36491 | 0.005787445328384771 | 5.970078728708109 |
p68 | 18943 | 26211 | 0.003371421843038767 | 6.058090122619244 |
p69 | 20417 | 25419 | 0.00577118309130166 | 6.400724363260939 |
p70 | 16798 | 38623 | 0.0044011698986977325 | 6.133120854312724 |
p71 | 16968 | 34113 | 0.004525640939452812 | 5.199443490503002 |
结果分析
从结果看到,模拟退火过程最后得到的结果并不是很好,原因可能在于模拟退火选择的初始态以及在寻找新解的过程都是随机的。可能的改进为:
- 使用贪心算法的结果作为初始值
- 产生新解的过程可以通过:
- 从中间往前改变
- 仅仅进行点交换
- 对于产生的较差的结果,可以通过爬山法的结果进行替换