题目描述
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<int,int> SortedCos[N];
bool LocalOK;
bool cmpGreedy(pair<int, int> a, pair<int, int> 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<int> FacNumList;
vector<int> 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 <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <utility>
#include <ctime>
#include <iomanip>
#include <vector>
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<int,int> 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<double> GTimers;
vector<double> SATimers;
vector<int> GResult;
vector<int> SAResult;
void solveGreedy(string pName);
bool cmpGreedy(pair<int, int> a, pair<int, int> 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<int> FacNumList;
vector<int> 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();
}