[算法Project] Capacited Facility Location Problem

这次博客的内容是关于我们算法课程的期末project:Capacited Facility Location Problem。我使用的是python语言求解。这道题乍一看像是一个线性规划问题,但最终我还是选择了贪心算法以及模拟退火算法。源代码即各种数据可见:github项目地址

文章目录

  • 一、问题描述
    • 二、问题分析
    • 三、贪心算法
    • 四、模拟退火(SA)

一、问题描述

[算法Project] Capacited Facility Location Problem_第1张图片
[算法Project] Capacited Facility Location Problem_第2张图片
[算法Project] Capacited Facility Location Problem_第3张图片

二、问题分析

首先,我们看看这个问题的约束条件以及目标函数。目标函数肯定是求总的opencost以及assigncost的最小值,而约束则是每个factory最终存放的所有customer的demand之和不能超过它自身capacity。
这样一来,我们首先可以定义一些全局变量了。

COST = 0 #目标函数
FACNUM = 0 #工厂数量
CUSNUM = 0 #顾客数量
CAPACITY = [] #理论最大值
LOAD = [] #实际装载量
OPENCOST = [] #每个fac的打开开销
STATUS = [] #工厂状态,打开为1,关闭为0
DEMAND = [] #每个顾客的需求
ASSIGNCOST = [] #每个工厂对于每个人存放其需求的开销
ASSIGN = [] #顾客将其需求存放在哪些工厂

然后,文件读取函数如下:

def readFile(filePath):
    global COST, FACNUM, CUSNUM, CAPACITY, LOAD, OPENCOST, DEMAND, ASSIGNCOST, STATUS, ASSIGN
    #清空
    COST = 0
    FACNUM = 0
    CUSNUM = 0
    CAPACITY = []
    OPENCOST = []
    DEMAND = []
    ASSIGNCOST = []

    fopen = open('Instances/' + filePath)
    line = fopen.readline().split()
    FACNUM = int(line[0])
    CUSNUM = int(line[1])
    STATUS = [0] * FACNUM
    ASSIGN = [0] * CUSNUM
    LOAD = [0] * FACNUM
    for i in range(FACNUM):
        line = fopen.readline().split()
        CAPACITY.append(int(line[0]))
        OPENCOST.append(int(line[1]))
    for i in range(int(CUSNUM/10)):
        line = fopen.readline().replace('.', '').split()
        for j in range(10):
            DEMAND.append(int(line[j]))

    for i in range(FACNUM):
        eachFac = []
        for j in range(int(CUSNUM/10)):
            line = fopen.readline().replace('.', '').split()
            for k in range(10):
                eachFac.append(int(line[k]))
        ASSIGNCOST.append(eachFac)
    #print(ASSIGNCOST)
    fopen.close()

接下来,由于此问题是一个NP完全问题,无法在多项式时间内找到最优解。于是我决定先使用Greedy算法找到一个比较不错的局部最优解,然后用其结果,再进行模拟退火,争取收敛到全局最优解。

三、贪心算法

贪心算法的思路很简单,遍历每个顾客,看看他们的需求还能放进哪些未满的工厂。接着在所有候选工厂中选择费用最低的那个并放入。
核心代码如下:

    for cus in range(CUSNUM):
        availableFac = []
        for fac in range(FACNUM):
            if CAPACITY[fac] >= DEMAND[cus]:
                availableFac.append(fac)
        if len(availableFac) == 0:
            return
        index = availableFac[0]
        minCost = ASSIGNCOST[index][cus]

        for fac in availableFac:
            tempCost = ASSIGNCOST[fac][cus]

            if tempCost < minCost:
                index = fac
                minCost = tempCost

        if STATUS[index] == 0:
            STATUS[index] = 1
            COST += minCost + OPENCOST[index]
        else:
            COST += minCost
        CAPACITY[index] -= DEMAND[cus]
        ASSIGN[cus] = index

注意,这个贪心的策略可能有不同,有些人说我们不应该光看这个顾客对于哪个工厂的费用最小,还要看那个工厂是否已经打开,如果没打开我们需要加工厂的打开费用。这里我想说的是,评估函数我做过三个实验:1.tempCost = ASSIGNCOST[fac][cus] 2. tempCost = ASSIGNCOST[fac][cus] + OPENCOST[fac][cus] 3.就像之前所说的,如果工厂没打开,则按照2式计算;工厂已经打开,则是按照1式进行计算。结果是,1式的最终结果最好。仔细思索了一下,应该是工厂少顾客多的缘故,顾客一多,我们就无需考略工厂的打开费用了。
贪心算法源代码:贪心算法
对于71个例子,结果和时间如下:

file result time
p1 9440 0.0
p2 8126 0.0
p3 10126 0.0
p4 12126 0.0009975433349609375
p5 9375 0.0
p6 8061 0.000997304916381836
p7 10061 0.0
p8 12061 0.0
p9 9040 0.0
p10 7726 0.0
p11 9726 0.0
p12 11726 0.0
p13 12032 0.0
p14 9180 0.0
p15 13180 0.0009620189666748047
p16 17180 0.000995635986328125
p17 12032 0.0
p18 9180 0.0
p19 13180 0.0
p20 17180 0.0009975433349609375
p21 12032 0.0009980201721191406
p22 9180 0.0
p23 13180 0.0
p24 17180 0.0
p25 19197 0.001996278762817383
p26 16131 0.000997304916381836
p27 21531 0.0009965896606445312
p28 26931 0.000997304916381836
p29 19305 0.0009975433349609375
p30 16239 0.000997304916381836
p31 21639 0.000997304916381836
p32 27039 0.0009975433349609375
p33 19055 0.000993490219116211
p34 15989 0.0009989738464355469
p35 21389 0.0009970664978027344
p36 26789 0.001995086669921875
p37 19055 0.000997304916381836
p38 15989 0.0009942054748535156
p39 21389 0.0009968280792236328
p40 26789 0.000997304916381836
p41 7226 0.0
p42 9957 0.0
p43 12448 0.0009970664978027344
p44 7585 0.0
p45 9848 0.0
p46 12639 0.0009970664978027344
p47 6634 0.0
p48 9044 0.0
p49 12420 0.0009984970092773438
p50 10062 0.0
p51 11351 0.0
p52 10364 0.0
p53 12470 0.000997304916381836
p54 10351 0.0
p55 11970 0.0009963512420654297
p56 23882 0.0009970664978027344
p57 32882 0.000997304916381836
p58 53882 0.0019948482513427734
p59 39121 0.0009980201721191406
p60 23882 0.000997304916381836
p61 32882 0.0009975433349609375
p62 53882 0.000997304916381836
p63 39121 0.001994609832763672
p64 23882 0.001994609832763672
p65 32882 0.000997304916381836
p66 53882 0.0009970664978027344
p68 23882 0.0009965896606445312
p69 32882 0.0009832382202148438
p70 53882 0.0019941329956054688
p71 39121 0.0019948482513427734

每个例子的具体结果:贪心算法测例具体结果

四、模拟退火(SA)

模拟0退火算法的步骤我就不详细说了,这个大家可以去自行百度。简而言之,对于贪心算法求出的结果,我用作了SA的初始解。邻域产生新解我使用了两种操作:1.随机选出一个工厂和顾客,把这个顾客的需求从原来的工厂放入随机选出的工厂。 2.随机选出两个顾客,把他们各自的需要放入对方的工厂。当然,这么操作可能会使某些工厂的承载超过capacity,那么就要重新选择顾客或者工厂了。
对于产生的新解,我们计算其新的cost。按照模拟退火算法,如果新解小于初始解,则更新它;否则,按照Metropolis准则判断是否接受这个解。
另外,退火的初温我选择了100,末温为1。退火速度为0.99。内层循环为100次。
模拟退火核心代码如下:

def SA():
    global COST, LOAD, ASSIGN, STATUS

    T0 = 100
    T = T0
    Tmin = 1
    alpha = 0.99
    innerIter = 100
    while T > Tmin:
        for i in range(innerIter):
            if np.random.rand() > 0.5: #选一个customo去别的fac
                randCus = np.random.randint(0, CUSNUM)
                initFacOfCus = ASSIGN[randCus]
                randFac = np.random.randint(0, FACNUM)
                while randFac == initFacOfCus:
                    randFac = np.random.randint(0, FACNUM)

                while LOAD[randFac] + DEMAND[randCus] > CAPACITY[randFac]:
                    randCus = np.random.randint(0, CUSNUM)
                    initFacOfCus = ASSIGN[randCus]
                    while randFac == initFacOfCus:
                        randFac = np.random.randint(0, FACNUM)
                dValue = ASSIGNCOST[randFac][randCus] - ASSIGNCOST[initFacOfCus][randCus]
                if dValue < 0:
                    COST += dValue
                    LOAD[randFac] += DEMAND[randCus]
                    LOAD[initFacOfCus] -= DEMAND[randCus]
                    if STATUS[randFac] == 0:
                        STATUS[randFac] = 1
                        COST += OPENCOST[randFac]
                    if LOAD[initFacOfCus] == 0:
                        STATUS[initFacOfCus] = 0
                        COST -= OPENCOST[initFacOfCus]
                    ASSIGN[randCus] = randFac
                else:
                    if np.random.rand() < np.exp(-(dValue) / T):  # 接受新解
                        COST += dValue
                        LOAD[randFac] += DEMAND[randCus]
                        LOAD[initFacOfCus] -= DEMAND[randCus]
                        if STATUS[randFac] == 0:
                            STATUS[randFac] = 1
                            COST += OPENCOST[randFac]
                        if LOAD[initFacOfCus] == 0:
                            STATUS[initFacOfCus] = 0
                            COST -= OPENCOST[initFacOfCus]
                        ASSIGN[randCus] = randFac

            else: #选两个cus对调fac
                randCus1 = 0
                randCus2 = 0
                while randCus1 == randCus2:
                    randCus1 = np.random.randint(0, CUSNUM)
                    randCus2 = np.random.randint(0, CUSNUM)
                initFac1 = ASSIGN[randCus1]
                initFac2 = ASSIGN[randCus2]

                while (LOAD[initFac1] - DEMAND[randCus1] + DEMAND[randCus2] > CAPACITY[initFac1]) or (LOAD[initFac2] - DEMAND[randCus2] + DEMAND[randCus1] > CAPACITY[initFac2]) :
                    randCus1 = 0
                    randCus2 = 0
                    while randCus1 == randCus2:
                        randCus1 = np.random.randint(0, CUSNUM)
                        randCus2 = np.random.randint(0, CUSNUM)
                    initFac1 = ASSIGN[randCus1]
                    initFac2 = ASSIGN[randCus2]
                dValue = ASSIGNCOST[initFac1][randCus2] + ASSIGNCOST[initFac2][randCus1] - ASSIGNCOST[initFac1][randCus1] - ASSIGNCOST[initFac2][randCus2]
                if dValue < 0:
                    COST += dValue
                    LOAD[initFac1] += DEMAND[randCus2] - DEMAND[randCus1]
                    LOAD[initFac2] += DEMAND[randCus1] - DEMAND[randCus2]
                    ASSIGN[randCus1] = initFac2
                    ASSIGN[randCus2] = initFac1
                else:
                    if np.random.rand() < np.exp(-(dValue) / T):  # 接受新解
                        COST += dValue
                        LOAD[initFac1] += DEMAND[randCus2] - DEMAND[randCus1]
                        LOAD[initFac2] += DEMAND[randCus1] - DEMAND[randCus2]
                        ASSIGN[randCus1] = initFac2
                        ASSIGN[randCus2] = initFac1
        T = T * alpha
        #print(T, COST)
    print("The Last Cost is:", COST)

SA算法源代码:SA算法
对于71个例子,结果和时间如下:

file result time
p1 9336 0.39295339584350586
p2 8009 0.40195631980895996
p3 10010 0.3580358028411865
p4 11942 0.39995670318603516
p5 9173 0.39095616340637207
p6 7891 0.3560817241668701
p7 9859 0.38598132133483887
p8 11861 0.3919527530670166
p9 9040 0.26628851890563965
p10 7727 0.27729058265686035
p11 9726 0.2722742557525635
p12 11727 0.26728224754333496
p13 12032 0.2682836055755615
p14 9180 0.2622981071472168
p15 13180 0.2642946243286133
p16 17180 0.26132726669311523
p17 12032 0.26126933097839355
p18 9180 0.2593350410461426
p19 13180 0.2573113441467285
p20 17180 0.2563450336456299
p21 12032 0.25829219818115234
p22 9180 0.260312557220459
p23 13180 0.2623291015625
p24 17180 0.26133203506469727
p25 18164 0.2712745666503906
p26 15141 0.27324533462524414
p27 19427 0.28224611282348633
p28 25475 0.27828550338745117
p29 19061 0.28325653076171875
p30 15713 0.2812771797180176
p31 20933 0.29720377922058105
p32 26148 0.28127551078796387
p33 19474 0.26928067207336426
p34 15433 0.26632142066955566
p35 21425 0.26628828048706055
p36 24729 0.2622997760772705
p37 17564 0.265291690826416
p38 15754 0.27227115631103516
p39 20944 0.2613034248352051
p40 26137 0.2603330612182617
p41 7100 0.31117701530456543
p42 9957 0.26332855224609375
p43 12458 0.25634098052978516
p44 7151 0.3241691589355469
p45 9859 0.2593069076538086
p46 12325 0.2543201446533203
p47 6318 0.4059157371520996
p48 9048 0.26628732681274414
p49 12873 0.26030492782592773
p50 9574 0.3400905132293701
p51 10900 0.2653172016143799
p52 10230 0.3670506477355957
p53 12795 0.3002490997314453
p54 9625 0.40192604064941406
p55 11893 0.2982041835784912
p56 23973 0.2593355178833008
p57 33049 0.26126980781555176
p58 54112 0.26030397415161133
p59 39409 0.26132631301879883
p60 24036 0.25232505798339844
p61 32994 0.26030397415161133
p62 53981 0.25834059715270996
p63 39205 0.2563159465789795
p64 24021 0.26030445098876953
p65 32977 0.2643263339996338
p66 53993 0.2563149929046631
p68 24030 0.2553138732910156
p69 33087 0.2583155632019043
p70 53954 0.2573122978210449
p71 39174 0.27825474739074707

每个例子的具体结果:SA算法测例具体结果

可以看到,模拟退火的例子相比于贪心算法的初始解,大部分的确有了小的进步;当然,还有些并不如贪心算法的初始解。可能这个题目的确比较适合用贪心算法把!

你可能感兴趣的:(LeetCode)