算法设计课程项目 工厂与顾客分配策略

项目背景:

有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个测试样例往后,没有输出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值