项目背景:
有m个工厂,每个工厂开设都有一个固定的费用open_cost及容量capacity,有n个客户,每个客户有一定的需求量demand,每个客户可以分配给任一个工厂且有相应的分配费用assign_cost,但是要求分配给每个工厂的客户总需求量不能超过工厂的容量。
目标:
求一个合理的分配策略,使得总的费用尽可能小。
测试样例数据处理:
因为测试样例数据较多,且他们的格式不统一,所以在编写算法前,我先对测试样例数据进行读取处理,把它们都用变量来表示。
数据处理代码如下:
void split(const string &s, vector<string> &v, const string &p) { //进行字符串分割
string::size_type pos1, pos2;
pos2 = s.find(p);
pos1 = 0;
while(string::npos != pos2) {
v.push_back(s.substr(pos1,pos2-pos1));
pos1=pos2 + p.size();
pos2=s.find(p, pos1);
}
if(pos1 != s.length())
v.push_back(s.substr(pos1));
}
//把测试数据用cap, fcost,demand, relcost 数组存,后面再把这些数组的数据放到算法所用的结构体中
void DealData(vector<int> &cap, vector<int> &fcost,
vector<int> &demand, vector<vector<int> > &relcost) {
int j=1,k=0;
int fnum=1 ,cnum;
string s;
stringstream ss;
ifstream myfile("Instances\\p13");
if(!myfile.is_open()){
cout << "open failed!";
}
while(getline(myfile, s)) {
ss.clear();
ss.str("");
vector<string> v;
if(j<=fnum+1)
split(s,v," ");
else if (j>fnum+1)
split(s,v,".");
if(j==1){
ss << v[0];
ss >> fnum;
ss.clear();
ss.str("");
ss << v[1];
ss >> cnum;
if(cnum>201 || cnum<0){
ss.clear();
ss.str("");
ss << v[2];
ss >> cnum;
}
}
else if(j>1 && j <= fnum+1) {
int c,f;
ss << v[0];
ss >> c;
ss.clear();
ss.str("");
ss << v[1];
ss >> f;
if(f<0 || f>3000){
ss.clear();
ss.str("");
ss << v[2];
ss >> f;
}
cap.push_back(c);
fcost.push_back(f);
}
else if(j >fnum+1 && j <= cnum/10 + fnum+1) {
int d;
for(int i=0; i<10; i++) {
ss << v[i];
ss >> d;
ss.clear();
ss.str("");
demand.push_back(d);
}
}
else if (j > cnum/10 + fnum+1 && j < (cnum/10)+fnum+2+(cnum*(fnum/10))){
int r;
relcost.push_back(vector<int>());
for(int i=0; i<10; i++) {
ss << v[i];
ss >> r;
relcost[k].push_back(r);
ss.clear();
ss.str("");
}
k++;
}
j++;
}
myfile.close();
}
算法1:模拟退火算法
模拟退火算法的实质是 通过多次尝试修改每个顾客从m个工厂中选择的工厂,如果找到则选择该方案,如果找到的解比原来的更差,则以一定的概率来接受该解,并且在每次尝试后,接受更差解的概率会逐渐降低,最终不会再接受更差解。因此找到的总的费用最小的分配策略实质还是一个局部最优解。但是模拟退火算法是有一定的可能性跳出某些局部最优解,从而找到更大范围内的一个局部最优解,甚至有可能找到一个全局最优解(虽然这个可能性非常非常小)。
代码如下:
#include <iostream>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <time.h>
#include <math.h>
#include <vector>
#define M 50 // 最大工厂数量
#define N 250 //最大客户数量
#define T 3000 //初始温度
#define EPS 1e-8 //终止温度
#define DELTA 0.97 //温度衰减率
#define OLOOP 20 //外循环 20次
#define ILOOP 100 //内循环 100次
#define LIMIT 1000 //概率选择上限
using namespace std;
//定义分配策略结构体
struct Assign {
int fact_state[M]; // 工厂状态,0为关闭,1为打开
int assign_fact[N]; // 每个顾客分配的工厂
int total_cost; //总的费用
};
//定义顾客结构体
struct Customer {
int demand; // 顾客的需求量
int ass_cost[M]; // 顾客分配给各工厂对应的分配费用
};
// 定义工厂结构体
struct Factory {
int capacity; // 工厂容量
int last_cap; // 工厂的剩余容量
int open_cost; // 开厂费用
int my_customer; // 工厂分配的客户数
};
//初始化最佳分配策略
Assign bestAssign = {
{0},
{0},
0
};
Customer C[N]; //顾客
Factory F[M]; // 工厂
int nCase; // 测试次数
//把测试样例数据输入工厂结构体中
void InputFactory(vector<int> cap, vector<int> fcost, int &m) {
int i;
for (i=0; i!= cap.size(); i++) {
F[i].capacity = cap[i];
F[i].last_cap = F[i].capacity;
F[i].open_cost = fcost[i];
F[i].my_customer = 0;
}
m = i;
}
//把测试样例数据输入顾客结构体中
void InputCustomer(vector<int> demand, vector<vector<int> > asscost ,int &n) {
int i;
for (i=0; i != demand.size(); i++) {
C[i].demand = demand[i];
for(int j = 0; j != asscost[i].size(); j++) {
C[i].ass_cost[j] = asscost[i][j];
}
}
n = i;
}
//先初始化一个分配方案,初始分配策略是按工厂号从前往后分配,前一个工厂容量不够才分配给后一个工厂
void Init(int m, int n) {
nCase = 0;
// bestAssign.total_cost = 0;
int t_demand=0;
// int j=0;
//给每个客户选一个工厂,按工厂号从小到大选
for(int i = 0; i < n; i++) {
for(int j=0; j<m; j++) {
if(F[j].last_cap >= C[i].demand) {
bestAssign.assign_fact[i] = j; //对顾客分配工厂
F[j].last_cap -= C[i].demand;
F[j].my_customer++;
if(bestAssign.fact_state[j] == 0) { //更改工厂状态
bestAssign.fact_state[j] = 1;
bestAssign.total_cost += F[j].open_cost + C[i].ass_cost[j];
} else {
bestAssign.total_cost += C[i].ass_cost[j];
}
break;
}
}
}
}
//打印结果,总共三行,第一行是总的费用、第二行是工厂的状态(1为打开,0为关闭)
//第三行是每个顾客分配的工厂号
void Print(Assign a, int m, int n) {
cout << a.total_cost << endl;
for(int i=0; i<m-1; i++) {
cout << a.fact_state[i] << " ";
}
cout << a.fact_state[m-1] << endl;
for(int i=0; i<n-1; i++) {
cout << a.assign_fact[i] << " ";
}
cout << a.assign_fact[n-1] << endl;
}
//更新分配方案,更新策略是从所有工厂中随机选一个新工厂,再随机选一个顾客,用新工厂替换该顾客所分配的工厂
//
Assign GetNext(Assign a, int m, int n) {
Assign ans = a;
int fnum = (int) (m * (rand() / (RAND_MAX + 1.0)));
int cnum = (int) (n * (rand() / (RAND_MAX + 1.0)));
while(fnum == ans.assign_fact[cnum] || F[fnum].last_cap < C[cnum].demand) { //让随机选出的新工厂满足顾客需求大于工厂剩余容量的条件
fnum = (int) (m * (rand() / (RAND_MAX + 1.0)));
}
F[ans.assign_fact[cnum]].last_cap += C[cnum].demand; //处理旧工厂
if ((--F[ans.assign_fact[cnum]].my_customer) == 0) { //处理旧工厂贡献的cost
ans.fact_state[ans.assign_fact[cnum]] = 0; //当旧工厂没有分配客户时,关闭工厂
ans.total_cost -= F[ans.assign_fact[cnum]].open_cost;
}
ans.total_cost -= C[cnum].ass_cost[ans.assign_fact[cnum]];
ans.assign_fact[cnum] = fnum; //分配新工厂
F[fnum].last_cap -= C[cnum].demand; //处理新工厂
F[fnum].my_customer++;
if (ans.fact_state[fnum] == 0) { //更新cost
ans.fact_state[fnum] = 1;
ans.total_cost += F[fnum].open_cost + C[cnum].ass_cost[fnum];
} else {
ans.total_cost += C[cnum].ass_cost[fnum];
}
nCase++;
return ans;
}
//退火算法,
void SA(int m, int n) {
int P_L = 0;
int P_F = 0;
double t = T;
srand((unsigned)(time(NULL)));
Assign curAssign = bestAssign;
Assign newAssign = bestAssign;
while(1) { //外循环,更新参数t,模拟退火过程
for(int i = 0; i < ILOOP; i++) { //内循环,寻找在一定温度下最优值
newAssign = GetNext(curAssign, m, n);
double dE = newAssign.total_cost - curAssign.total_cost;
if (dE < 0) { //找到更优值则更新分配方案
curAssign = newAssign;
P_L = 0;
P_L = 0;
} else { //如果找到的解更差,则以一定概率接受该解,接受概率会越来越小
double rd = rand() / (RAND_MAX + 1.0);
if (exp(dE / t) > rd && exp(dE / t) < 1)
curAssign = newAssign;
P_L++;
}
if (P_L > LIMIT) { //选择次数超过上限则放弃
P_F++;
break;
}
}
if (curAssign.total_cost < bestAssign.total_cost)
bestAssign = curAssign;
if (P_F > OLOOP || t < EPS) {
break;
}
t *= DELTA;
}
}
int main() {
int m, n;
vector<int> cap, fcost, demand;
vector<vector<int> > relcost;
DealData(cap, fcost, demand, relcost);
InputFactory(cap,fcost,m);
InputCustomer(demand, relcost,n);
Init(m,n);
SA(m,n);
Print(bestAssign,m,n);
return 0;
}
测试结果:
p1
p2
p3
p4
p5
p6-p10
p20-25
p30-p34
p60-p63
p68-p71
算法2: 贪心算法:
首先是对每个工厂选择分配费用最小的客户,因为最优解肯定包含最小分配费用的工厂的(此处有个问题是这样处理每个工厂最终都打开)。然后对剩余未分配工厂的顾客选择分配费用最小的工厂。
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#define M 30 // 最大工厂数量
#define N 300 // 最大的顾客数
using namespace std;
//定义分配策略结构体
struct Assign {
int fact_state[M]; // 工厂状态,0为关闭,1为打开
int assign_fact[N]; // 每个顾客分配的工厂
int total_cost; //总的费用
};
//定义顾客结构体
struct Customer {
int demand; // 顾客的需求量
int ass_cost[M]; // 顾客分配给各工厂对应的分配费用
bool assigned; //顾客的工厂分配标志, 1代表已分配
};
// 定义工厂结构体
struct Factory {
int capacity; // 工厂容量
int last_cap; // 工厂的剩余容量
int open_cost; // 开厂费用
int my_customer; // 工厂分配的客户数
};
//初始化最佳分配策略
Assign bestAssign = {
{0},
{0},
0
};
Customer C[N];
Factory F[M];
//进行字符串分割
void split(const string &s, vector<string> &v, const string &p) {
string::size_type pos1, pos2;
pos2 = s.find(p);
pos1 = 0;
while(string::npos != pos2) {
v.push_back(s.substr(pos1,pos2-pos1));
pos1=pos2 + p.size();
pos2=s.find(p, pos1);
}
if(pos1 != s.length())
v.push_back(s.substr(pos1));
}
//把测试样例数据输入工厂结构体中
void InputFactory(vector<int> cap, vector<int> fcost, int &m) {
int i;
for (i=0; i!= cap.size(); i++) {
F[i].capacity = cap[i];
F[i].last_cap = F[i].capacity;
F[i].open_cost = fcost[i];
F[i].my_customer = 0;
}
// m = i;
}
//把测试样例数据输入顾客结构体中
void InputCustomer(vector<int> demand, vector<vector<int> > asscost, int &n) {
int i;
for (i=0; i != demand.size(); i++) {
C[i].demand = demand[i];
C[i].assigned = 0;
for(int j = 0; j != asscost[i].size(); j++) {
C[i].ass_cost[j] = asscost[i][j];
}
}
// n = i;
}
//打印结果,总共三行,第一行是总的费用、第二行是工厂的状态(1为打开,0为关闭)
//第三行是每个顾客分配的工厂号
void Print(Assign a, int m, int n) {
cout << a.total_cost << endl;
for(int i=0; i<m-1; i++) {
cout << a.fact_state[i] << " ";
}
cout << a.fact_state[m-1] << endl;
for(int i=0; i<n-1; i++) {
cout << a.assign_fact[i] << " ";
}
cout << a.assign_fact[n-1] << endl;
}
//贪心算法 先找出顾客中分配给各工厂所需分配费用最小的那位顾客
//然后在剩余的顾客中寻找分配费用最小的那个工厂
void Greedy(vector<vector<int> > asscost, int m, int n) {
int min, t;
for(int i=0; i<m; i++) { //先找出顾客中分配给各工厂所需分配费用最小的那位顾客
t=0;
min = asscost[0][i];
for(int j = 1; j<n; j++) {
if (asscost[j][i] < min) {
min = asscost[j][i];
t = j; //配费用最小的顾客
}
}
if (C[t].demand <= F[i].last_cap) { //工厂须满足顾客需求大于工厂剩余容量的条件
C[t].assigned = 1;
bestAssign.assign_fact[t] = i;
bestAssign.fact_state[i] = 1;
bestAssign.total_cost += F[i].open_cost + C[t].ass_cost[i];
F[i].my_customer++;
F[i].last_cap -= C[t].demand;
}
}
for(int i=0; i<n; i++) { //在剩余的顾客中寻找分配费用最小的那个工厂
if (!C[i].assigned) {
t=0;
min = asscost[i][0];
for(int j=1; j<m; j++) {
if (asscost[i][j] < min) {
min = asscost[i][j];
t = j; //配费用最小的工厂
}
}
if (C[i].demand <= F[t].last_cap) { ///工厂须满足顾客需求大于工厂剩余容量的条件
C[i].assigned = 1;
bestAssign.assign_fact[i] = t;
if(bestAssign.fact_state[t] == 0){
bestAssign.fact_state[t] = 1;
bestAssign.total_cost += F[t].open_cost + C[i].ass_cost[t];
} else {
bestAssign.total_cost += C[i].ass_cost[t];
}
F[t].my_customer++;
F[t].last_cap -= C[i].demand;
}
}
}
}
void DealData(vector<int> &cap, vector<int> &fcost,
vector<int> &demand, vector<vector<int> > &relcost, int &fnum, int &cnum) {
ifstream myfile("Instances\\p27");
int j=1,k=0, l=0;
fnum=1;
if(!myfile.is_open()){
cout << "open failed!";
}
string s;
stringstream ss;
while(getline(myfile, s)) {
ss.clear();
ss.str("");
vector<string> v;
if (j<=fnum+1){
split(s,v," ");
if(j==1){
ss << v[0];
ss >> fnum;
ss.clear();
ss.str("");
ss << v[1];
ss >> cnum;
if(cnum>201|| cnum<0){
ss.clear();
ss.str("");
ss << v[2];
ss >> cnum;
}
}
else if(j>1 && j <= fnum+1) {
int c,f;
ss << v[0];
ss >> c;
ss.clear();
ss.str("");
ss << v[1];
ss >> f;
if(f<0 || f>2500){
ss.clear();
ss.str("");
ss << v[2];
ss >> f;
}
cap.push_back(c);
fcost.push_back(f);
}
}
else if(j >fnum+1 && j <= cnum/10 + fnum+1) {
int d;
split(s,v,".");
for(int i=0; i<10; i++) {
ss << v[i];
ss >> d;
ss.clear();
ss.str("");
demand.push_back(d);
}
}
else if( j > cnum/10 + fnum+1 && j <= cnum*(fnum/10) + cnum/10 + fnum+1) {
int d;
split(s,v,".");
if (k==0)
relcost.push_back(vector<int>());
for(int i=0; i<10; i++) {
ss << v[i];
ss >> d;
ss.clear();
ss.str("");
relcost[l].push_back(d);
}
k++;
if(k== fnum/10){
k=0;
l++;
}
}
j++;
}
myfile.close();
}
int main() {
int fnum, cnum;
vector<int> cap, fcost, demand;
vector<vector<int> > relcost;
DealData(cap, fcost, demand, relcost, fnum, cnum);
InputFactory(cap,fcost,fnum);
InputCustomer(demand, relcost ,cnum);
Greedy(relcost, fnum, cnum);
Print(bestAssign,fnum, cnum);
return 0;
}
测试结果:
p1-p10:
p13-p15
p23-p26
p31-p33
p45-p47
p53-p55
p56
第二个贪心算法发现在测试前25个测试样例的时候cost在10000左右,呈现稳定递增态势。
但是在第25个样例开始cost突降到6000左右,也呈现稳定递增态势。
在第56个测试样例往后,没有输出