[算法设计与分析-期末项目] Capacitated Facility Location Problem

题目描述

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.

分析

将 m 个顾客分给 n 个设施,每个设施有打开费用 OpenCost,每个顾客对每个设施有不同的指派费用 AssignCost,每个设施都有自己的容量限制 Capacity, 每个顾客有自己的需求量 Demand。决定开放哪些设施并对顾客进行指派使得满足所有顾客的情况下,花费的费用最小

方法一:贪心

贪心可以在很快的速度下求出一个相对优的解。
将所有顾客的 AssignCost 从小到大排序,对于每个顾客,优先选择能容纳他的 AssignCost 最小的设施。

//  n : numFacility, m : numCustomer
int n, m;   
const int N = 55, M = 305;  

//  Capacity, OpeningCost, Demand, AssignmentCost
int cap[N], opc[N];    
int dmd[M], asc[M][N]; 

//  algorithm time
clock_t StartTime, EndTime;

//  mem for solve 
int FacOpenNum;
int CostAns;
bool FacOpen[N];
int FacCap[N];
int AssAns[M];
pair SortedCos[N];
bool LocalOK;

bool cmpGreedy(pair a, pair b) {
    return a.first < b.first;
}

// greedy
void solveGreedy(string pName) {
    StartTime = clock();
    // init
    CostAns = 0;
    FacOpenNum = 0;
    memset(FacOpen, false, sizeof(FacOpen));
    memset(FacCap, 0, sizeof(FacCap));
    memset(AssAns, 0, sizeof(AssAns));
    for (int i = 0; i < m; i++) {
        //   sort assignmentcost
        for (int j = 0; j < n; j++) {
            SortedCos[j].first = asc[i][j];
            SortedCos[j].second = j;
        }
        sort(SortedCos, SortedCos + n, cmpGreedy);
        //  choose a fac to assign
        for (int j = 0; j < n; j++) {
            int NowCos = SortedCos[j].first;
            int FacNum = SortedCos[j].second;
            // cout << NowCos << endl;
            //  can assign
            if (FacCap[FacNum] + dmd[i] <= cap[FacNum]) {
                if (!FacOpen[FacNum]) {
                    FacOpenNum++;
                    CostAns += opc[FacNum];
                    FacOpen[FacNum] = true;
                }
                FacCap[FacNum] += dmd[i];
                CostAns += NowCos;
                AssAns[i] = FacNum + 1;
                break;
            }
        }
    }
    EndTime = clock();
}

方法二:模拟退火

模拟退火算法维持一个温度 T,在某一温度下进行多次邻域搜索,如果新解优于旧解,则接受;如果弱于旧解,则概率接受。
该概率描述为
其中
每进行一批邻域搜索后进行降温,一般描述为 , 通常 选择 [ 0.5, 0.99 ] 中的某个值。

对于该问题,我们设计邻域操作时需要考虑将所有的状态包含进去。
基于此,设计以下四种邻域:
1)将某个顾客移动到另一个设施
2)将两个设施的某两个顾客交换
3)打开一个关闭的工厂并将某个顾客移入该工厂
4)关闭一个打开的工厂并将其所有顾客随机移入其他工厂

这四种邻域以相等概率进行。
关于不合法的邻域,采用直接抛弃的办法,并进行下一次邻域。
每次邻域维护一堆局部状态量,如果合格,将其复制到全局最优状态量中。
每个温度下进行 200000 次 邻域操作

初解采用贪心解。

//  mem for SA Local srch
int SA_FON;
bool SA_FO[N];
int SA_FC[N];
int SA_AA[M];

double probSA(double temp, double delta) {
    return exp(-delta / temp);
}
double randSA() {
    return rand() / (double)RAND_MAX;
}
int solveLocal();
double coldSA(double temp) {
    return temp * 0.9;
}
void cpyCenterToLocal() {
    SA_FON = FacOpenNum;
    for (int i = 0; i < n; i++) {
        SA_FO[i] = FacOpen[i];
        SA_FC[i] = FacCap[i];
    }
    for (int i = 0; i < m; i++) {
        SA_AA[i] = AssAns[i];
    }
}
void cpyLocalToCenter() {
    FacOpenNum = SA_FON;
    for (int i = 0; i < n; i++) {
        FacOpen[i] = SA_FO[i];
        FacCap[i] = SA_FC[i];
    }
    for (int i = 0; i < m; i++) {
        AssAns[i] = SA_AA[i];
    }
}

// simulation Annealing
void solveSA(string pName) {
    // greedy to init
    // include StartTime
    solveGreedy(pName);
    // SA init
    double T = 100;
    srand(time(NULL));
    // SA
    while (T > 0.1) {
        // Local Srch 200000 times
        for (int i = 0; i < 200000; i++) {
            int NewCost = solveLocal();
            if ( randSA() <= probSA(T, NewCost - CostAns) && LocalOK) {
                // change to new state
                CostAns = NewCost;
                cpyLocalToCenter();
            }
        }
        // cold temparature
        T = coldSA(T);
    }
    // renew EndTime
    EndTime = clock();
}

int solveLocal() {
    srand(time(NULL));
    int NewCost = CostAns;
    LocalOK = true;
    cpyCenterToLocal();
    double WayProb = randSA();
    if (WayProb >= 0 && WayProb < 0.25) {
        //  choose way 1: change a cus to another fac
        int CusNum = randSA() * m;
        int FacNum = randSA() * n;
        //  fac is not open or cannot accept the cus
        // directly return
        if (!SA_FO[FacNum] || SA_FC[FacNum] + dmd[CusNum] > cap[FacNum]) {
            return NewCost;
        }
        int OldFac = SA_AA[CusNum];
        SA_AA[CusNum] = FacNum;
        SA_FC[FacNum] += dmd[CusNum];
        SA_FC[OldFac] -= dmd[CusNum];
        NewCost = NewCost + asc[CusNum][FacNum] - asc[CusNum][OldFac];
    } else if (WayProb >= 0.25 && WayProb < 0.5) {
        // choose way 2: switch 2 cus
        int Cus1 = randSA() * m;
        int Cus2 = randSA() * m;
        int Fac1 = SA_AA[Cus1];
        int Fac2 = SA_AA[Cus2];
        // cannot switch 
        // directly return
        if (Cus1 == Cus2 ||
                SA_FC[Fac1] - dmd[Cus1] + dmd[Cus2] > cap[Fac1] ||
                SA_FC[Fac2] - dmd[Cus2] + dmd[Cus1] > cap[Fac2]   ) {
            return NewCost;    
        }
        SA_AA[Cus1] = Fac2;
        SA_AA[Cus2] = Fac1;
        SA_FC[Fac1] = SA_FC[Fac1] - dmd[Cus1] + dmd[Cus2];
        SA_FC[Fac2] = SA_FC[Fac2] - dmd[Cus2] + dmd[Cus1];
        NewCost = NewCost + (asc[Cus1][Fac2] - asc[Cus1][Fac1])
                          + (asc[Cus2][Fac1] - asc[Cus2][Fac2]);
    } else if (WayProb >= 0.5 && WayProb < 0.75) {
        // choose way 3: open a fac and change 1 cus to it
        // no closed fac to open
        if (SA_FON == n) return NewCost;
        int FacNum = randSA() * n;
        // randomed fac is open
        if (SA_FO[FacNum]) {
            return NewCost;
        }
        int CusNum = randSA() * m;
        // cannot accept
        if (SA_FC[FacNum] + dmd[CusNum] > cap[FacNum]) {
            return NewCost;
        }
        int OldFac = SA_AA[CusNum];
        SA_FON++;
        SA_FO[FacNum] = true;
        SA_FC[FacNum] += dmd[CusNum];
        SA_FC[OldFac] -= dmd[CusNum];
        SA_AA[CusNum] = FacNum;
        NewCost = NewCost - asc[CusNum][OldFac] + asc[CusNum][FacNum] + opc[FacNum];
    } else {
        // choose way 4: close a fac and random its cus to others
        // no open fac to close
        if (SA_FON == 0) return NewCost;
        int FacNum = randSA() * n;
        // randomed fac is closed
        if (!SA_FO[FacNum]) {
            return NewCost;
        }
        // record cus in the fac
        vector FacNumList;
        vector NewFacList;
        for (int i = 0; i < m; i++) {
            if (SA_AA[i] == FacNum) {
                FacNumList.push_back(i);
            }
        }
        // random cus to other facs
        SA_FON--;
        SA_FO[FacNum] = false;
        SA_FC[FacNum] = 0;
        int NowNewCost = NewCost;
        NowNewCost -= opc[FacNum];
        for (int i = 0; i < FacNumList.size(); i++) {
            int CusNum = FacNumList[i];
            int NewFac = randSA() * n;
            // cannot accept
            // cnt to record iter times, limit be sure can jump out
            int cnt = 1;
            int limit = 10 * n;
            while ( (!SA_FO[NewFac] || SA_FC[NewFac] + dmd[CusNum] > cap[NewFac]) && cnt < limit ) {
                NewFac = randSA() * n;
                cnt++;
            }
            // prob cannot close the fac to accept people
            // directly return
            if (cnt >= limit) {
                LocalOK = false;
                return NewCost;
            }
            SA_FC[NewFac] += dmd[CusNum]; 
            SA_AA[CusNum] = NewFac;
            NowNewCost = NowNewCost - asc[CusNum][FacNum] + asc[CusNum][NewFac];
        }
        NewCost = NowNewCost;
    }
    return NewCost;
}

结果及对比

结果及算法时间

数据组数 贪心算法结果 贪心算法时间 模拟退火结果 模拟退火时间
p1 10355 0.000 10355 6.233
p2 9041 0.000 9041 5.256
p3 11041 0.000 11041 5.407
p4 13041 0.000 13041 5.223
p5 10827 0.000 10827 5.183
p6 9513 0.000 8627 6.223
p7 11513 0.000 8145 6.501
p8 13513 0.000 10876 6.768
p9 10355 0.000 8179 5.380
p10 9041 0.000 5831 5.282
p11 11041 0.000 7369 6.410
p12 13041 0.001 7322 6.249
p13 11915 0.000 10494 6.249
p14 9426 0.000 6997 6.118
p15 13026 0.001 9549 6.685
p16 16626 0.000 10554 7.903
p17 11915 0.000 10128 6.312
p18 9426 0.000 7854 6.051
p19 13026 0.000 11372 6.284
p20 16626 0.000 14411 6.182
p21 11915 0.000 10429 6.069
p22 9426 0.000 8193 7.565
p23 13026 0.000 10106 6.125
p24 16626 0.000 12443 6.075
p25 16673 0.001 12738 18.353
p26 14143 0.001 8497 14.289
p27 18943 0.001 14576 18.137
p28 23743 0.001 20231 20.747
p29 17463 0.001 14094 17.326
p30 14726 0.001 8146 18.188
p31 19926 0.000 15329 22.010
p32 25126 0.001 19371 16.434
p33 16597 0.001 8870 15.846
p34 14067 0.001 12635 21.744
p35 18867 0.001 15239 17.863
p36 23667 0.000 15990 15.962
p37 16597 0.001 13719 17.454
p38 14067 0.001 12791 18.602
p39 18867 0.001 15292 17.765
p40 23667 0.001 10325 17.017
p41 8853 0.000 7717 9.596
p42 10681 0.001 9512 10.004
p43 13592 0.000 11804 8.789
p44 12280 0.000 11241 16.254
p45 12949 0.000 10727 8.770
p46 14646 0.000 12122 9.176
p47 14162 0.000 12553 13.164
p48 13980 0.000 12692 9.619
p49 12846 0.001 11286 10.089
p50 10106 0.000 9351 21.064
p51 10778 0.000 9393 12.079
p52 15442 0.000 14814 10.625
p53 14438 0.000 12706 9.445
p54 15941 0.000 14769 9.980
p55 14113 0.000 12384 12.059
p56 14822 0.000 9642 18.622
p57 23522 0.001 14442 17.736
p58 43822 0.001 25228 18.119
p59 29230 0.001 18318 18.707
p60 14822 0.000 9466 16.490
p61 23522 0.001 15856 17.382
p62 43822 0.001 30125 16.425
p63 29230 0.000 20544 16.490
p64 14822 0.001 10469 16.411
p65 23522 0.001 13895 18.075
p66 43822 0.001 28710 19.358
p67 31192 0.001 17227 22.205
p68 14822 0.001 7813 22.066
p69 23522 0.000 14995 20.138
p70 43822 0.001 28849 18.134
p71 29230 0.001 16904 17.714

贪心算法具体结果

石墨文档

模拟退火具体结果

石墨文档

代码总览

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

//  n : numFacility, m : numCustomer
int n, m;   
const int N = 55, M = 305;  

//  Capacity, OpeningCost, Demand, AssignmentCost
int cap[N], opc[N];    
int dmd[M], asc[M][N]; 

//  algorithm time
clock_t StartTime, EndTime;

//  mem for solve 
int FacOpenNum;
int CostAns;
bool FacOpen[N];
int FacCap[N];
int AssAns[M];
pair SortedCos[N];
bool LocalOK;

//  mem for SA Local srch
int SA_FON;
bool SA_FO[N];
int SA_FC[N];
int SA_AA[M];

//  mem for results
vector GTimers;
vector SATimers;
vector GResult;
vector SAResult;

void solveGreedy(string pName);
bool cmpGreedy(pair a, pair b) {
    return a.first < b.first;
}
void solveSA(string pName);
void inputData(string FileName);
void outputGreedy(string pName);
void outputSA(string pName);
double probSA(double temp, double delta) {
    return exp(-delta / temp);
}
double randSA() {
    return rand() / (double)RAND_MAX;
}
int solveLocal();
double coldSA(double temp) {
    return temp * 0.9;
}
void cpyCenterToLocal() {
    SA_FON = FacOpenNum;
    for (int i = 0; i < n; i++) {
        SA_FO[i] = FacOpen[i];
        SA_FC[i] = FacCap[i];
    }
    for (int i = 0; i < m; i++) {
        SA_AA[i] = AssAns[i];
    }
}
void cpyLocalToCenter() {
    FacOpenNum = SA_FON;
    for (int i = 0; i < n; i++) {
        FacOpen[i] = SA_FO[i];
        FacCap[i] = SA_FC[i];
    }
    for (int i = 0; i < m; i++) {
        AssAns[i] = SA_AA[i];
    }
}

void outputGTimers() {
    ofstream GTOut;
    GTOut.open("GResult");
    for (int i = 0; i < GTimers.size(); i++) {
        GTOut << "p" + to_string(i + 1) << " ";
        GTOut << GResult[i] << " ";
        GTOut << fixed << setprecision(3) << GTimers[i] << endl;
    }
}

void outputSATimers() {
    ofstream SATOut;
    SATOut.open("SAResult");
    for (int i = 0; i < SATimers.size(); i++) {
        SATOut << "p" + to_string(i + 1) << " ";
        SATOut << SAResult[i] << " ";
        SATOut << fixed << setprecision(3) << SATimers[i] << endl;
    }
}

int main() {
    for (int i = 1; i <= 71; i++) {
        string pName = "p" + to_string(i);
        string fileName = "Instances/" + pName;
        inputData(fileName);
        // solveGreedy(pName);
        // outputGreedy(pName);
        solveSA(pName);
        outputSA(pName);
        
    }
    // outputGTimers();
    outputSATimers();
    // system("pause");
    return 0;
}

// greedy
void solveGreedy(string pName) {
    StartTime = clock();
    // init
    CostAns = 0;
    FacOpenNum = 0;
    memset(FacOpen, false, sizeof(FacOpen));
    memset(FacCap, 0, sizeof(FacCap));
    memset(AssAns, 0, sizeof(AssAns));
    for (int i = 0; i < m; i++) {
        //   sort assignmentcost
        for (int j = 0; j < n; j++) {
            SortedCos[j].first = asc[i][j];
            SortedCos[j].second = j;
        }
        sort(SortedCos, SortedCos + n, cmpGreedy);
        //  choose a fac to assign
        for (int j = 0; j < n; j++) {
            int NowCos = SortedCos[j].first;
            int FacNum = SortedCos[j].second;
            // cout << NowCos << endl;
            //  can assign
            if (FacCap[FacNum] + dmd[i] <= cap[FacNum]) {
                if (!FacOpen[FacNum]) {
                    FacOpenNum++;
                    CostAns += opc[FacNum];
                    FacOpen[FacNum] = true;
                }
                FacCap[FacNum] += dmd[i];
                CostAns += NowCos;
                AssAns[i] = FacNum + 1;
                break;
            }
        }
    }
    EndTime = clock();
}

// simulation Annealing
void solveSA(string pName) {
    // greedy to init
    // include StartTime
    solveGreedy(pName);
    // SA init
    double T = 100;
    srand(time(NULL));
    // SA
    while (T > 0.1) {
        // Local Srch 200000 times
        for (int i = 0; i < 200000; i++) {
            int NewCost = solveLocal();
            if ( randSA() <= probSA(T, NewCost - CostAns) && LocalOK) {
                // change to new state
                CostAns = NewCost;
                cpyLocalToCenter();
            }
        }
        // cold temparature
        T = coldSA(T);
    }
    // renew EndTime
    EndTime = clock();
}

int solveLocal() {
    srand(time(NULL));
    int NewCost = CostAns;
    LocalOK = true;
    cpyCenterToLocal();
    double WayProb = randSA();
    if (WayProb >= 0 && WayProb < 0.25) {
        //  choose way 1: change a cus to another fac
        int CusNum = randSA() * m;
        int FacNum = randSA() * n;
        //  fac is not open or cannot accept the cus
        // directly return
        if (!SA_FO[FacNum] || SA_FC[FacNum] + dmd[CusNum] > cap[FacNum]) {
            return NewCost;
        }
        int OldFac = SA_AA[CusNum];
        SA_AA[CusNum] = FacNum;
        SA_FC[FacNum] += dmd[CusNum];
        SA_FC[OldFac] -= dmd[CusNum];
        NewCost = NewCost + asc[CusNum][FacNum] - asc[CusNum][OldFac];
    } else if (WayProb >= 0.25 && WayProb < 0.5) {
        // choose way 2: switch 2 cus
        int Cus1 = randSA() * m;
        int Cus2 = randSA() * m;
        int Fac1 = SA_AA[Cus1];
        int Fac2 = SA_AA[Cus2];
        // cannot switch 
        // directly return
        if (Cus1 == Cus2 ||
                SA_FC[Fac1] - dmd[Cus1] + dmd[Cus2] > cap[Fac1] ||
                SA_FC[Fac2] - dmd[Cus2] + dmd[Cus1] > cap[Fac2]   ) {
            return NewCost;    
        }
        SA_AA[Cus1] = Fac2;
        SA_AA[Cus2] = Fac1;
        SA_FC[Fac1] = SA_FC[Fac1] - dmd[Cus1] + dmd[Cus2];
        SA_FC[Fac2] = SA_FC[Fac2] - dmd[Cus2] + dmd[Cus1];
        NewCost = NewCost + (asc[Cus1][Fac2] - asc[Cus1][Fac1])
                          + (asc[Cus2][Fac1] - asc[Cus2][Fac2]);
    } else if (WayProb >= 0.5 && WayProb < 0.75) {
        // choose way 3: open a fac and change 1 cus to it
        // no closed fac to open
        if (SA_FON == n) return NewCost;
        int FacNum = randSA() * n;
        // randomed fac is open
        if (SA_FO[FacNum]) {
            return NewCost;
        }
        int CusNum = randSA() * m;
        // cannot accept
        if (SA_FC[FacNum] + dmd[CusNum] > cap[FacNum]) {
            return NewCost;
        }
        int OldFac = SA_AA[CusNum];
        SA_FON++;
        SA_FO[FacNum] = true;
        SA_FC[FacNum] += dmd[CusNum];
        SA_FC[OldFac] -= dmd[CusNum];
        SA_AA[CusNum] = FacNum;
        NewCost = NewCost - asc[CusNum][OldFac] + asc[CusNum][FacNum] + opc[FacNum];
    } else {
        // choose way 4: close a fac and random its cus to others
        // no open fac to close
        if (SA_FON == 0) return NewCost;
        int FacNum = randSA() * n;
        // randomed fac is closed
        if (!SA_FO[FacNum]) {
            return NewCost;
        }
        // record cus in the fac
        vector FacNumList;
        vector NewFacList;
        for (int i = 0; i < m; i++) {
            if (SA_AA[i] == FacNum) {
                FacNumList.push_back(i);
            }
        }
        // random cus to other facs
        SA_FON--;
        SA_FO[FacNum] = false;
        SA_FC[FacNum] = 0;
        int NowNewCost = NewCost;
        NowNewCost -= opc[FacNum];
        for (int i = 0; i < FacNumList.size(); i++) {
            int CusNum = FacNumList[i];
            int NewFac = randSA() * n;
            // cannot accept
            // cnt to record iter times, limit be sure can jump out
            int cnt = 1;
            int limit = 10 * n;
            while ( (!SA_FO[NewFac] || SA_FC[NewFac] + dmd[CusNum] > cap[NewFac]) && cnt < limit ) {
                NewFac = randSA() * n;
                cnt++;
            }
            // prob cannot close the fac to accept people
            // directly return
            if (cnt >= limit) {
                LocalOK = false;
                return NewCost;
            }
            SA_FC[NewFac] += dmd[CusNum]; 
            SA_AA[CusNum] = NewFac;
            NowNewCost = NowNewCost - asc[CusNum][FacNum] + asc[CusNum][NewFac];
        }
        NewCost = NowNewCost;
    }
    return NewCost;
}

//   handle input
void inputData(string FileName) {
    // cout << FileName << endl;
    ifstream pi;
    pi.open(FileName.c_str());
    char dot;

    pi >> n >> m;
    // cout << n << endl;
    // cout << m << endl;
    // cout << FileName << endl;
    for (int i = 0; i < n; i++) {
        pi >> cap[i] >> opc[i];
        // cout << cap[i] << endl;
    }
    for (int i = 0; i < m; i++) {
        pi >> dmd[i] >> dot;
        // cout << dmd[i] << endl;
    }
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            pi >> asc[i][j] >> dot;
        }
    }
    pi.close();
}

void outputGreedy(string pName) {
    string FoutName = "GAnswers/" + pName;
    ofstream po;
    po.open(FoutName);

    GResult.push_back(CostAns);
    po << CostAns << endl;
    for (int i = 0; i < n; i++) {
        po << FacOpen[i] << " ";
    }
    po << endl;
    for (int i = 0; i < m; i++) {
        po << AssAns[i] << " ";
    }
    po << endl;

    double timeUsed = (double) (EndTime - StartTime) / CLOCKS_PER_SEC;
    // po << fixed << setprecision(3) << timeUsed << endl;
    GTimers.push_back(timeUsed);

    po.close();
}

void outputSA(string pName) {
    string FoutName = "SAAnswers/" + pName;
    ofstream po;
    po.open(FoutName);

    SAResult.push_back(CostAns);
    po << CostAns << endl;
    for (int i = 0; i < n; i++) {
        po << FacOpen[i] << " ";
    }
    po << endl;
    for (int i = 0; i < m; i++) {
        po << AssAns[i] << " ";
    }
    po << endl;

    double timeUsed = (double) (EndTime - StartTime) / CLOCKS_PER_SEC;
    // po << fixed << setprecision(3) << timeUsed << endl;
    SATimers.push_back(timeUsed);

    po.close();
}

你可能感兴趣的:([算法设计与分析-期末项目] Capacitated Facility Location Problem)