1 题目描述
2 题目分析及解题思路
2.1 开发环境
- MATLAB R2016a
由于本题目会使用大量的向量、矩阵操作,为了方便及高效运算,使用MATLAB开发。
2.2 解题思路
2.2.1 解题方法
针对本题目,准备使用三种方法来解,分别是
Local Search 顾客选择工厂
、Local Search 工厂选择顾客
、模拟退火
。
2.2.2 编码方式
在使用三种方法解得过程中,编码方式是十分重要的,结合此题目,使用字符串来表示解,形式为:角标对应顾客序号,数组值对应所在工厂序号。
例如解:1 2 1 3....
代表:1号顾客分配去1号厂;2号顾客分配去2号厂;3号顾客分配去1号厂;4号顾客分配去3号厂…
2.3 Local Search 顾客选择工厂
查看几个算例后发现,其实题目中的例子开设工厂和顾客参与工厂的开销差别不是很大,因此,采取了优先顾客的原则,对于每一位顾客,将他们安排到开销最小的工厂中去,如果该工厂已经满员,那么就安排他去开销第二小的工厂,以此类推。
2.4 Local Search 工厂选择顾客
同样的,如果能够省下工厂的开销,那么也会是一笔不小的数额,因此,采取有限工厂的原则,对于每一个工厂,选择开销最小的顾客直到工厂满员。首先开启第一间工厂,依次选择开销最小的顾客直到满员;之后开启第二间工厂,直到所有的顾客都被安排的明明白白。
2.5 模拟退火算法
在每一轮随机生成新的解,如果生成的新的解比现有的解更好,则接受;否则以一定的概率接受。此概率随着迭代的进行不断减少。
当迭代到一定次数,或连续多次没有接收更加优质的解,视为寻找到最优解。
3 算法实现
3.1 Local Search 顾客选择工厂
FOR i = 1: CustomeNum
WHILE TRUE
FIND_MIN_COST_FACTORY
IF FACTORY_CAPILITY > CUSTOMER_CAPILITYFACTORY -= CUSTOMER_CAPILITY
BREAKENDIF
ENDWHILE
ENDFOR
3.2 Local Search 工厂选择顾客
ARRANGE_NUM = 0;
OPEN_FACTORY = 1;
WHILE ARRANGE != CUSTOMER_NUMFIND_MIN_COST_CUSTOMER
IF FACTORY_CAPILITY > CUSTOMER_CAPILITYFACTORY -= CUSTOMER_CAPILITY
RESULT(CUSTOMER) = OPEN_FACTORY
CONTINUEENDIF
OPEN_FACTORY++ENDWHILE
3.3 模拟退火算法
3.3.1 参数设计
由于算例较多,因此设计一套使用所有的参数体系。
参数 | 设计 |
---|---|
T(初温) | 随机生成(顾客数/10)个解,取花费的均值的平方 |
MIN_T(最低温) | T的倒数 |
FORBID_MAX (最大无用解个数) | 顾客数 * 工厂数 ^ 2 |
dropSpeed(降温速率) | 0.99 |
INSIDE_NUM(内层循环数) | 顾客数 * 工厂数 |
T(初温) | 随机生成(顾客数/10)个解,取花费的均值的平方 |
3.3.2 算法
INITIAL(SOLUTION)
WHILE FORBID_NUM < FORBID_MAX && T > MIN_TFOR i = 1: INSIDE_NUM
NEWSOLUTE = CREATE_NEW_SOLUTION(SOLUTION)
IF ESTIMATE(NEWSOLUTION) < ESTIMATE(SOLUTION)SOLUTION = NEWSOLUTION
FORBID_NUM = 0ELSE
RATE = RANDOM(0, 1)
IF RATE < EXP(-DELT(E )/ T)SOLUTION = NEWSOLUTION
ELSE
FORBID_NUM++
ENDIF
ENDIF
ENDFOR
T *= DROP_SPEEDENDWHILE
3.3.3 产生新解过程
采取3种新解的方法:
- 1.随机改变一个顾客所在工厂。
- 2.随机改变N(N < CUSTOMER_NUM)个顾客所在工厂。
- 3.随机对任意一段进行排序。
3.3.4 修正解
只修正错误顾客。
从第一位顾客开始记录,当工厂容量不足,将超额的顾客序号记录。
对于超额的顾客,依次安排在剩余未超额的花费最小的工厂中。
3.4 Local Search作为初始的模拟退火算法
与模拟算法类似,但使用了Local Search 的解作为初始解。
4 关键代码
4.1 Local Search 顾客选择工厂
% arrange every customer
for i = 1: cusNum
while 1
% find the most adaptor function of this customer
x = find(tempCus(i, :) == min(tempCus(i, :)));
x = x(1, 1);
% function has capiility, arrange; else, put this value to INF
if tempCap(x, 1) - cusCap(i, 1) > 0
tempCap(x, 1) = tempCap(x, 1) - cusCap(i, 1);
result(i, 1) = x;
break
else
tempCus(i, x) = 10000;
end
end
end
4.2 Local Search 工厂选择顾客
while 1
% if all of customer has been arranged
if currentCus == cusNum
break;
end
% find the most adaptor customer
x = find(tempCus(:, currentFunc) == min(tempCus(:, currentFunc)));
x = x(1, 1);
% if function has capility, arrange customer
if tempCap(currentFunc, 1) - cusCap(x, 1) > 0
tempCap(currentFunc, 1) = tempCap(currentFunc, 1) - cusCap(x, 1);
result(x, 1) = currentFunc;
tempCus(x, :) = 10000;
currentCus = currentCus + 1;
continue;
end
% if current function conn't aaccept customer, open a new function
tempCus(:, currentFunc) = 10000;
currentFunc = currentFunc + 1
end
4.3 模拟退火算法
4.3.1 主要框架
%外循环
%当温度足够高并且产生的连续废弃解不够多就继续迭代
while T > minT && wrongRes < generateNum
%内循环
for i = 1 : cusNum * funcNum
%生成新解
newSolu = newSolution( solution, cap, cusCap, cusCost );
deltaE = estimate( solution, openCost, cusCost ) - estimate( newSolu, openCost, cusCost );
%新解很好,可以使用,否则一定概率抛弃
if deltaE > 0
solution = newSolu;
wrongRes = 0;
else
%如果低于接受概率,需要抛弃,并增加失败解的数量;否则接受
randomRate = unifrnd (0,1,1);
acceptRate = exp(-deltaE / T);
if randomRate > acceptRate
solution = newSolu;
wrongRes = 0;
else
wrongRes = wrongRes + 1;
end
end
end
%降温并记录迭代次数
T = T * dropSpeed;
iterateNum = iterateNum + 1;
% fprintf('iterateNum: %d, solution: %f\n', iterateNum, estimate( solution, openCost, cusCost ));
end
4.3.2 产生新解
4.3.3 修改解
% find all customer who cann't be arrange
for i = 1: cusNum
tempCap(source(i, 1), 1) = tempCap(source(i, 1), 1) - cusCap(i, 1);
if tempCap(source(i, 1), 1) < 0
wrong = [wrong; i];
end
end
tempCus = cusCost;
% function cann;t accept customer whose capility is minus
for i = 1: funcNum
if tempCap(i, 1) < 0
tempCus(:, i) = 10000;
end
end
% adjust wrong customer location
tempCus = cusCost;
[wrongNum, ~] = size(wrong);
for i = 1: wrongNum
currentCus = wrong(i, 1);
while 1
% find the most adaptor function of this customer
x = find(tempCus(currentCus, :) == min(tempCus(currentCus, :)));
x = x(1, 1);
% function has capiility, arrange; else, put this value to INF
if tempCap(x, 1) - cusCap(currentCus, 1) > 0
tempCap(x, 1) = tempCap(x, 1) - cusCap(currentCus, 1);
result(currentCus, 1) = x;
break
else
tempCus(currentCus, x) = 10000;
end
end
end
4.4 其他
4.4.1 数据读取与解析
% read file
data = textread(fileName);
% initial function number and customer number
funcNum = data(1,1);
cusNum = data(1,2);
% initial function capitial and open cost
cap = zeros(funcNum, 1);
openCost = zeros(funcNum, 1);
for i = 1: funcNum
cap(i, 1) = data(i + 1, 1);
openCost(i, 1) = data(i + 1, 2);
end
% initial customer capility and cost
rpc = cusNum / 10;
rpf = funcNum / 10;
% get customer capility
cusCap = data(funcNum + 2: funcNum + rpc + 1, :);
% transform and reshape
cusCap = reshape(cusCap', cusNum, 1);
% get customer cost
cusCost = zeros(cusNum, funcNum);
% get data
tempCost = data(funcNum + cusNum / 10 + 2 : funcNum + (cusNum + cusNum * funcNum) / 10 + 1, :);
%reshape
for i = 1: cusNum
cusCost(i, :) = reshape(tempCost((i - 1) * rpf + 1: i * rpf, :)', funcNum, 1);
end
4.4.2 价值评估
% get cost of customer
for i = 1: m
cost = cost + cusCost(i, result(i, 1));
end
func = unique(result);
% get open function number
[openFuncNum, ~] = size(func);
% get cost of function
for i = 1: openFuncNum
cost = cost + openCost(func(i, 1), 1);
end
5 错误数据修改
给出的算例中,p27、p47有严重错误。修改如下:
- 1.将p27数据后的无用数据全部删除
- 2.将p47数据的缺失数据用下一行相应位置补齐,如图:
6 结果分析
6.1 Local Search比较
首先比较两个
Local search
的结果,很明显的发现,第一种的结果要好于第二种;且两种算法的时间都很短。
6.2 LocalSearch与模拟退火比较
很明显的看出,模拟退火需要大量的时间,但是得到的解普遍要优于Local Search。
6.3 使用Local Search作为初始解的模拟退火
很明显的发现,用时短了很多,但是得到的解质量却没有随机生成的解好。