[算法Project] Capacited Facility Location Problem

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

一、问题描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、问题分析

首先,我们看看这个问题的约束条件以及目标函数。目标函数肯定是求总的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个例子,结果和时间如下:

fileresulttime
p194400.0
p281260.0
p3101260.0
p4121260.0009975433349609375
p593750.0
p680610.000997304916381836
p7100610.0
p8120610.0
p990400.0
p1077260.0
p1197260.0
p12117260.0
p13120320.0
p1491800.0
p15131800.0009620189666748047
p16171800.000995635986328125
p17120320.0
p1891800.0
p19131800.0
p20171800.0009975433349609375
p21120320.0009980201721191406
p2291800.0
p23131800.0
p24171800.0
p25191970.001996278762817383
p26161310.000997304916381836
p27215310.0009965896606445312
p28269310.000997304916381836
p29193050.0009975433349609375
p30162390.000997304916381836
p31216390.000997304916381836
p32270390.0009975433349609375
p33190550.000993490219116211
p34159890.0009989738464355469
p35213890.0009970664978027344
p36267890.001995086669921875
p37190550.000997304916381836
p38159890.0009942054748535156
p39213890.0009968280792236328
p40267890.000997304916381836
p4172260.0
p4299570.0
p43124480.0009970664978027344
p4475850.0
p4598480.0
p46126390.0009970664978027344
p4766340.0
p4890440.0
p49124200.0009984970092773438
p50100620.0
p51113510.0
p52103640.0
p53124700.000997304916381836
p54103510.0
p55119700.0009963512420654297
p56238820.0009970664978027344
p57328820.000997304916381836
p58538820.0019948482513427734
p59391210.0009980201721191406
p60238820.000997304916381836
p61328820.0009975433349609375
p62538820.000997304916381836
p63391210.001994609832763672
p64238820.001994609832763672
p65328820.000997304916381836
p66538820.0009970664978027344
p68238820.0009965896606445312
p69328820.0009832382202148438
p70538820.0019941329956054688
p71391210.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个例子,结果和时间如下:

fileresulttime
p193360.39295339584350586
p280090.40195631980895996
p3100100.3580358028411865
p4119420.39995670318603516
p591730.39095616340637207
p678910.3560817241668701
p798590.38598132133483887
p8118610.3919527530670166
p990400.26628851890563965
p1077270.27729058265686035
p1197260.2722742557525635
p12117270.26728224754333496
p13120320.2682836055755615
p1491800.2622981071472168
p15131800.2642946243286133
p16171800.26132726669311523
p17120320.26126933097839355
p1891800.2593350410461426
p19131800.2573113441467285
p20171800.2563450336456299
p21120320.25829219818115234
p2291800.260312557220459
p23131800.2623291015625
p24171800.26133203506469727
p25181640.2712745666503906
p26151410.27324533462524414
p27194270.28224611282348633
p28254750.27828550338745117
p29190610.28325653076171875
p30157130.2812771797180176
p31209330.29720377922058105
p32261480.28127551078796387
p33194740.26928067207336426
p34154330.26632142066955566
p35214250.26628828048706055
p36247290.2622997760772705
p37175640.265291690826416
p38157540.27227115631103516
p39209440.2613034248352051
p40261370.2603330612182617
p4171000.31117701530456543
p4299570.26332855224609375
p43124580.25634098052978516
p4471510.3241691589355469
p4598590.2593069076538086
p46123250.2543201446533203
p4763180.4059157371520996
p4890480.26628732681274414
p49128730.26030492782592773
p5095740.3400905132293701
p51109000.2653172016143799
p52102300.3670506477355957
p53127950.3002490997314453
p5496250.40192604064941406
p55118930.2982041835784912
p56239730.2593355178833008
p57330490.26126980781555176
p58541120.26030397415161133
p59394090.26132631301879883
p60240360.25232505798339844
p61329940.26030397415161133
p62539810.25834059715270996
p63392050.2563159465789795
p64240210.26030445098876953
p65329770.2643263339996338
p66539930.2563149929046631
p68240300.2553138732910156
p69330870.2583155632019043
p70539540.2573122978210449
p71391740.27825474739074707

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值