Capacitated Facility Location Problem

Capacitated Facility Location Problem

问题描述

  • Suppose there are n facilities and m customers. We wish to choose:
    • which of the n facilities to open
    • 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.

在这里插入图片描述

算法设计

贪心算法

对于每个顾客,寻找满足其需求的代价最小的设施。若设施未开启,则代价为开启该设施费用和为其分配需求的费用之和。由于为前面的顾客分配后会影响后面的顾客的分配,因此贪心算法与顾客分配顺序有关。可以多次使用贪心算法求解,每次先打乱顾客分配次序,取最优解。

伪代码如下:

for i in 0 to times
	shuffle customers order
	for c in customers
		choose the facility f with the least cost
		update solution
	update optimal_solution

局部搜索

首先利用贪心算法求得一个初始解,然后在这个解的基础上求邻域解。若该邻域解的总代价小于原解,则讲解替换为该邻域解,然后在新解的基础上继续求邻域解;若该邻域解的总代价大于原解,则将该解舍去。重复搜索邻域解较大次数,最终可得到一个局部最优解。

寻找邻域解有以下几种方法:

  1. 随机将1名顾客分配给另一个设施。
  2. 随机交换2名顾客分配的设施。
  3. 随机选2名顾客,将这两名顾客间的所有顾客分配的设施反转,例如顾客编号为[0, 1, 2, 3, 4, 5]:
    • 若选择顾客1和4:将顾客1,4分配的设施交换;将顾客2,3分配的设施交换。
    • 若选择顾客4和1:将顾客4,1分配的设施交换;将顾客5,0分配的设施交换。
    • 在交换过程中,若出现设施剩余容量不能满足交换时,跳过交换这两名顾客,继续交换其余顾客。

伪代码如下:

for i in 0 to times
	get a neighborhood solution s
	if s.cost < current_solution.cost
		current_solution = s

模拟退火法

由于局部搜索法的解只向较好解的方向移动,因此很容易陷入局部最优中。而模拟退火法在局部搜索法的基础上增加了接受差解的情况:若邻域解比当前解差,以一定的概率接受该差解。温度越高,接受差解的概率越高,随着温度的降低,接受差解的概率趋于0。在模拟退火法的初始阶段,温度较高,解能够自由的在解空间移动;随着温度的降低,解向劣解移动的概率逐渐降低,因此最终求得的解是一个局部最优解。模拟退火法与局部搜索法相比,两者最终都是求得一个局部最优解;但模拟退火法初期能够允许接受差解,因此能够跳过某些局部最优解,最终得到一个较好的局部最优解。

伪代码如下:

T = T0
current_solution = s0
while T > Tmin
	for i in 0 to times
		get a neighborhood solution s
		if newCost < cost or random() < exp(-(newCost - cost) / T)
			current_solution = s
	T *= rate  /*rate < 1*/

程序代码(C++)

具体代码可见github: https://github.com/Huang-Junjie/CFLP

数据结构:

struct solution {
	int cost;
	vector<bool> isOpen;
	vector<int> assign;
	vector<int> capacityLeft;
};


struct problem {
	int facilityNum;
	int customerNum;
	vector<int> capacity;
	vector<int> openingCost;
	vector<int> demand;
	//assignmentCost[i][j] is the cost of allocating all the demand of customer i to facility j.
	vector<vector<int>> assignmentCost;
};

贪心法

void greedy() {
	vector<int> result;
	vector<float> runtime;
	for (int i = 1; i < 72; i++) {
		cout << "---------------------------" << endl;
		cout << "P" << i << ":" << endl;
		srand((unsigned int)(time(NULL)));
		long startTime = clock();
		problem pro;
		solution sol;
		readInstance("Instances/p" + to_string(i), pro);
		getInitalSolution(pro, sol);
		//初始化一个未开始分配的解
		solution emptySol;
		emptySol.isOpen.resize(pro.facilityNum);
		emptySol.assign.resize(pro.customerNum);
		emptySol.capacityLeft.resize(pro.facilityNum);
		emptySol.cost = 0;
		for (bool i : emptySol.isOpen) i = false;
		for (int i = 0; i < pro.facilityNum; i++) emptySol.capacityLeft[i] = pro.capacity[i];
		//初始化分配顺序
		vector<int> order(pro.customerNum);
		for (int i = 0; i < order.size(); i++) order[i] = i;

		int index;
		int temp;
		pair<int, int> leastAssign;
		solution newSol;
		//打乱顺序使用贪心法,取最优值
		for (int j = 0; j < 1000; j++) {
			//打乱分配顺序
			for (int i = 0; i < order.size() - 1; i++) {
				index = rand() % (order.size() - i) + i;
				temp = order[i];
				order[i] = order[index];
				order[index] = temp;
			}
			//使用贪心法进行分配
			newSol = emptySol;
			for (int i = 0; i < pro.customerNum; i++) {
				leastAssign = findLeastCostFacility(pro, newSol, order[i]);
				newSol.isOpen[leastAssign.first] = true;
				newSol.assign[order[i]] = leastAssign.first;
				newSol.capacityLeft[leastAssign.first] -= pro.demand[order[i]];
				newSol.cost += leastAssign.second;
			}
			if (newSol.cost < sol.cost) sol = newSol;
			cout << "p" << i << ": times: " << j << endl;
			printSolution(sol);
		}
		result.push_back(sol.cost);
		runtime.push_back((clock() - startTime) / CLK_TCK);
		writeSolution("Results/Greedy/p" + to_string(i) + ".txt", sol);
	}
	wirteResultTable("Results/GreedyTable.csv", result, runtime);
}

Result table: https://github.com/Huang-Junjie/CFLP/blob/master/Results/GreedyTable.csv

Detailed solution: https://github.com/Huang-Junjie/CFLP/tree/master/Results/Greedy

局部搜索法

void localSearch() {
	vector<int> result;
	vector<float> runtime;
	for (int i = 1; i < 72; i++) {
		cout << "---------------------------" << endl;
		cout << "P" << i << ":" << endl;
		srand((unsigned int)(time(NULL)));
		long startTime = clock();
		problem pro;
		solution sol;
		readInstance("Instances/p" + to_string(i), pro);
		getInitalSolution(pro, sol);
		solution newSol(sol);
		for (int j = 0; j < 100000; j++) {
			//寻找邻域解
			getNewSolution(pro, newSol);
			//若新解cost小,则直接接受
			if (newSol.cost < sol.cost) {
				sol = newSol;
			}
			else newSol = sol;
			if (j % 1000 == 0) {
				cout << "p" << i << ": times: " << j << endl;
				printSolution(sol);
			}
		}
		result.push_back(sol.cost);
		runtime.push_back((clock() - startTime) / CLK_TCK);
		writeSolution("Results/LS/p" + to_string(i) + ".txt", sol);

	}
	wirteResultTable("Results/LSTable.csv", result, runtime);
}

Result table: https://github.com/Huang-Junjie/CFLP/blob/master/Results/LSTable.csv

Detailed solution: https://github.com/Huang-Junjie/CFLP/tree/master/Results/LS

模拟退火法

int main()
{	
	vector<int> result;
	vector<float> runtime;
	double Tmax = 100;
	double Tmin = 0.1;
	int times = 1500;
	double rate = 0.99;
	for (int i = 1; i < 72; i++) {
		cout << "---------------------------" << endl;
		cout << "P" << i << ":" << endl;
		srand((unsigned int)(time(NULL)));
		long startTime = clock();
		problem pro;
		solution sol;
		readInstance("Instances/p" + to_string(i), pro);
		getInitalSolution(pro, sol);
		solution newSol(sol);
		double T = Tmax;
		while (T > Tmin) {
			for (int i = 0; i < times; i++) {
				//寻找邻域解
				getNewSolution(pro, newSol);
				//若新解cost小,则直接接受
				//若新解cost大,则以exp(-(newcost - cost)/ T)概率接受
				if(newSol.cost < sol.cost || 
					rand() / (RAND_MAX + 1.0) < exp((sol.cost - newSol.cost) / T)) {
					sol = newSol;
				}
				else newSol = sol;
				/*cerr << i << endl;*/
			}
			T *= rate;
			cout << "T: " << T << endl;
			printSolution(sol);
		}
		result.push_back(sol.cost);
		runtime.push_back((clock() - startTime) / CLK_TCK);
		writeSolution("Results/SA/p" + to_string(i) + ".txt", sol);
		
	}
	wirteResultTable("Results/SATable.csv", result, runtime);
}

Result table: https://github.com/Huang-Junjie/CFLP/blob/master/Results/SATable.csv

Detailed solution: https://github.com/Huang-Junjie/CFLP/tree/master/Results/SA

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值