关于Max-Minsum Dispersion Problem的介绍详见之前推文模拟退火(SA)算法求解Max-Minsum Dispersion Problem(附代码及详细注释)
禁忌搜索算法(Tabu Search,TS)是由美国科罗拉多大学的Fred Glover教授于1986年提出的可用于有效解决组合优化问题的一种智能优化算法。
禁忌搜索是Local Search(LS)的扩展,是一种全局逐步寻优的全局性邻域搜索算法,其核心思想是:通过禁忌策略实现记忆功能,通过破禁准则继承LS的强局部搜索能力。种种机制的配合,使得TS一方面具备高局部搜索能力,同时又能防止算法在优化寻优过程中陷入局部最优。
禁忌搜索的主要构成要素是:
对于禁忌搜索各部分内容,公众号之前的推文中已经介绍过多次了。还不了解的同学可以先移步干货 | 到底是什么算法,能让人们如此绝望?了解算法细节。
Attibute-based tabu search是一种流行的元启发式方法(meta-heuristic),被广泛用于解决NP-hard组合问题,这种方法在禁忌搜索的过程中选择禁忌解的某个特征,即属性。相对来说,研究较少的Solution-based tabu search是则记录访问过的整个解。相比之下,记录解比记录某个属性更“精确”,不容易将未访问过的解错误地禁忌。
然而,一般的禁忌解会导致算法的运行时间大幅增加,因为判定两个解是否完全相同通常需要很长时间。利用哈希表将数字对应到解上是一种减少时间消耗的有效方法。哈希表通过哈希函数将数字映射到解上,在对比两个解是否相同时只需要对比哈希值即可。本文的算法就利用了哈希表进行对solution的禁忌。
本文中,小编参考文末所列文献中的方法,利用Solution-based tabu search来求解Max-Minsum DP,该算法依靠hash vectors来有效地确定候选解的禁忌状态,并通过一个受约束的交换邻域来保证算法的高计算效率。
Solution-based tabu search的伪代码如下:
其中M*表示至今发现的最优解,
即hash vectors,也就是本问题的禁忌表,
为相对应的hash functions。
如图所示,首先初始化hash vectors(第2-6行)并生成初始解M(第7行)。然后,算法多次迭代以改进初始解(第9-18行)。在每次迭代中,该算法首先从当前解的邻域
确定最佳非禁忌邻居解M',然后使用M'取代当前解M。随后,根据新解M更新hash vectors(即禁忌表)(第15-17行)。重复while循环,直到达到时间限制(
)。最后,将搜索过程中找到的最优解M∗作为最终结果返回。
友情提示:以下3.1-3.3部分请先参见之前推文模拟退火(SA)算法求解Max-Minsum Dispersion Problem(附代码及详细注释),以免跨度过大,晦涩难懂。
每次迭代的初始解,我们亦是随机产生,随机选择m个元素构成M,未被选中的即为N\M。利用
中代表解,如果
,则
=1,否则
=0。故初始解InitiaSol由随机挑选集合N中m个元素产生。 (专业代码及算例可以从http://www.info.univ-angers.fr/~hao/MaxMinsumdp.html下载)
采用exchange算子:从被选择的元素的集合中随机选择元素u,即u∈M,从不被选择的元素的集合中随机选择元素v,即v∈N\M,交换u, v。
将一次邻域动作定义为<u,v>,则对当前解对应M进行一次邻域动作得到的邻居解为M⊕<u,v>,
即为一次操作所有可能的邻居解,可以表示为
={M⊕<u,v>:u∈M,v∈N\M}。
就本问题而言,邻域
大小正好等于m×(n−m),对于相对较大的n和m(比方说几百个元素),搜索的邻域会变得非常大,使得算法运行速度变慢。我们通过参数约束交换邻域
来减小计算量。所谓“参数约束”就是指每次搜索时仅搜索由参数大小决定的邻域的子集,而不是整个邻域。其中,
可以表示为
={M⊕<u,v>:u∈X⊂M,v∈Y⊂N\M}。
为了加快搜索过程,我们将需要被交换的元素被限制为两个高质量子集X和Y,为了识别也可说是得到这两个高质量子集,我们利用n维向量
,其中
,则
,对于集合M,根据其∆值快速排序得到降序排列的集合M的元素,前ρ*|M|个元素作为集合X,对于集合N\M,根据其∆值快速排序得到升序排列的集合N\M的元素,前ρ*|N\M|个元素作为集合Y,其中ρ为预定的参数,构造约束邻域的总计算复杂度为O(mlogm+(n−m)log(n−m)),相对较低。参数约束交换邻域的大小等于O(
×m(n−m)),当ρ小于0.5时复杂度大幅度减小。
对于本问题,给定邻域解和对应向量
,目标值
可以在O(M)时间内计算,此外,若是两个元素u∈M,v∈N\M交换,则向量
可以在O(N)时间内快速更新,具体可表示为下图:
值得一提的是,本问题解法中,我们已提前计算每个未选中元素到所有被选中元素的距离和,更新的新元素v对应Δ值可以直接以之减去被替换元素u与新元素v的距离,十分方便。
SBTS算法依赖于三个hash functions及其关联的hash vectors来有效地确定候选解的禁忌状态,对于给定的hash functions即h和大小为L的hash vectors即H,h可以用来映射候选解,使得
的二进制值标识解x的禁忌状态:
表示解x以前被访问过并且被标记为禁忌状态(因此,除非满足破禁标准,否则x在当前迭代中被排除在考虑范围之外),而
表示x没有被访问过,因此有资格在当前迭代中被考虑。此外,通过使用三个hash functions和数量颇多的hash vectors,我们可以显著降低误分类候选解的禁忌状态的概率。
是对三个hash function采用不同值的参数,
是hash vector的长度, 被设置为
。
作为候选解,如果
,则
=1,否则为0。三个hash function定义如下:
hash vectors
如下进行初始化和更新。
在搜索开始时,hash vectors
被初始化为0,这意味着禁忌列表不禁忌任何解。
然后,随着搜索的进行,hash vectors
被动态更新:一旦当前解被其邻域解之一替换,则
。
最后,有一个关键问题是如何使用这些hash vectors和相关的hash functions来确定候选解的禁忌状态。在我们的算法的每次迭代中,对于邻域中的每个解x,我们首先检查
的值。如果它们都取值为1,则认为解x以前被访问过,因此禁止再次考虑。否则,解x被确定为非禁忌解。作为说明性示例,下图出示禁忌列表禁止的先前访问的解x(因为
、
和
都取值为1)。另一方面,任何至少有一个
值等于0的解都是当前迭代的合格解,意即可以被访问。
不难看出,solution-based tabu search的关键就在于同时使用三个hash function,函数本身的简单设计使得其可以快速更新,同时多次使用强化效果,显著降低误分类候选解的概率。对于当前解x的邻居解x⊕<u,v>,其中u∈M和v∈N\M,
(x⊕<u,v>)的值可以在O(1)中计算得到,即
,这意味着给定解的邻居解的禁忌状态可以在O(1)时间内确定。然而,对于初始解
,我们需要从头开始计算相应的hash values
,复杂度为O(N)。
在这里小编为大家搜集了,文献中常用的6组140个基准实例(分别为APOM set,SOM-b set,GDK-c set,DM1a set,DM1c set,DM2 set)用于测试代码,并简单修改了论文作者提供的代码。参考论文、算例和代码的下载请移步留言区。
算例MDP_Andrade_Instances\andrade_mdptabu search\01a050m10.dat的部分内容如图:
经小编修改并注释的核心代码如下:
void Swap_Tabu_Search(int Sol[], int NSol[], double P[], double* F)
{
//register表示使用CPU内部寄存器,可以加快变量读写速度
register int swap, NM;
register double p, f_c, p_min;
register double tabu_best_fc, best_fc; //禁忌状态的最优解及当前最优解
int non_improve = 0; // the stop conditon of TS
int best_x[50] = { 0 }, best_y[50] = { 0 }, x = 0, y = 0;
int tabu_best_x[50] = { 0 }, tabu_best_y[50] = { 0 };
NM = N - M;
int Hx1 = 0, Hx2 = 0, Hx3 = 0;
int Hx11 = 0, Hx22 = 0, Hx33 = 0;
double current_time, starting_time;
//公式hash funcions
for (int i = 0;i < M;i++)
{
Hx1 += W1[Sol[i]];
Hx2 += W2[Sol[i]];
Hx3 += W3[Sol[i]];
}
Build_Potential(Sol, NSol, P);
//拷贝,将当前解作为最优解
f_best = f;
for (int i = 0;i < M;i++) {
SS_BEST.S[i] = Sol[i];
//cout << SS_BEST.S[i] << " ";
}
//cout << endl;
for (int i = 0;i < NM;i++) {
SS_BEST.NS[i] = NSol[i];
//cout << SS_BEST.NS[i] << " ";
}
//cout << endl;
for (int i = 0;i < N;i++) {
SS_BEST.P[i] = P[i];
//cout << SS_BEST.P[i] << " ";
}
//cout << endl;
//记录时间
starting_time = clock();
int iter = 0;
current_time = (double)(1.0 * (clock() - starting_time) / CLOCKS_PER_SEC);
//the number of tabu neighbors and non-tabu neighbors
int num_tabu_best = 0, num_best = 0;
while (current_time < time_limit)
{
tabu_best_fc = -9999999.0;
best_fc = -9999999.0;
num_tabu_best = 0;
num_best = 0;
//排序以得高质量子集X、Y,否则全搞m*(n-m)太大了
for (int i = 0;i < M;i++)
dataM[i] = P[Sol[i]];
Quick_Sort(dataM, M, Sol);
for (int i = 0;i < NM;i++)
dataNM[i] = P[NSol[i]];
Quick_Sort(dataNM, NM, NSol);
//a) Evaluating the neighborhood
for (int x = 0.3 * M;x >= 0;x--)
{
for (int y = 0.7 * NM;y < NM;y++)
{
p_min = 9999999.0;
//其他P[Sol[i]]需将与Sol[x]的距离转换为与Sol[y]的距离
for (int i = 0;i < x;i++)
{
p = P[Sol[i]] - D[Sol[i]][Sol[x]] + D[Sol[i]][NSol[y]];
p_min = p < p_min ? p : p_min;
}
//自身P[Sol[i]]的改变
p = P[NSol[y]] - D[NSol[y]][Sol[x]];
p_min = p < p_min ? p : p_min;
//其他P[Sol[i]]需将与Sol[x]的距离转换为与Sol[y]的距离
for (int i = x + 1;i < M;i++)
{
p = P[Sol[i]] - D[Sol[i]][Sol[x]] + D[Sol[i]][NSol[y]];
p_min = p < p_min ? p : p_min;
}
f_c = p_min;
Hx11 = Hx1 + (W1[NSol[y]] - W1[Sol[x]]);
Hx22 = Hx2 + (W2[NSol[y]] - W2[Sol[x]]);
Hx33 = Hx3 + (W3[NSol[y]] - W3[Sol[x]]);
//禁忌状态
if (H1[Hx11 % L] == 1 && H2[Hx22 % L] == 1 && H3[Hx33 % L] == 1)
{
if (f_c > tabu_best_fc) //为打破禁忌做准备
{
tabu_best_x[0] = x;
tabu_best_y[0] = y;
tabu_best_fc = f_c;
num_tabu_best = 1;
}
else if (f_c == tabu_best_fc && num_tabu_best < 50)
{
tabu_best_x[num_tabu_best] = x;
tabu_best_y[num_tabu_best] = y;
num_tabu_best++;
}
}
else
{
if (f_c > best_fc)
{
best_x[0] = x;
best_y[0] = y;
best_fc = f_c;
num_best = 1;
}
else if (f_c == best_fc && num_best < 50)
{
best_x[num_best] = x;
best_y[num_best] = y;
num_best++;
}
}
}
}
//b. Moves
if ((num_tabu_best > 0 && tabu_best_fc > best_fc && tabu_best_fc > f_best + 1.0e-7) || num_best == 0) // aspiration criterion
{
//无路可走,打破禁忌
f = tabu_best_fc;
int select = rand() % num_tabu_best;
int u = tabu_best_x[select];
int v = tabu_best_y[select];
//u,v交换
Swap_Update_Potential(P, Sol[u], NSol[v]);
Hx1 = Hx1 + W1[NSol[v]] - W1[Sol[u]];
Hx2 = Hx2 + W2[NSol[v]] - W2[Sol[u]];
Hx3 = Hx3 + W3[NSol[v]] - W3[Sol[u]];
H1[Hx1 % L] = 1;
H2[Hx2 % L] = 1;
H3[Hx3 % L] = 1;
swap = Sol[u];
Sol[u] = NSol[v];
NSol[v] = swap;
}
// non tabu
else
{
f = best_fc;
int select = rand() % num_best;
int u = best_x[select];
int v = best_y[select];
Swap_Update_Potential(P, Sol[u], NSol[v]);
Hx1 = Hx1 + W1[NSol[v]] - W1[Sol[u]];
Hx2 = Hx2 + W2[NSol[v]] - W2[Sol[u]];
Hx3 = Hx3 + W3[NSol[v]] - W3[Sol[u]];
H1[Hx1 % L] = 1;
H2[Hx2 % L] = 1;
H3[Hx3 % L] = 1;
swap = Sol[u];
Sol[u] = NSol[v];
NSol[v] = swap;
}
//c. print
iter++;
current_time = (double)(1.0 * (clock() - starting_time) / CLOCKS_PER_SEC);
if (f >= f_best + 1.0e-7) //浮点数无法精确表达
{
if (f > f_best + 1.0e-7)
{
f_best = f;
for (int i = 0;i < M;i++)
SS_BEST.S[i] = Sol[i];
for (int i = 0;i < NM;i++)
SS_BEST.NS[i] = NSol[i];
for (int i = 0;i < N;i++)
SS_BEST.P[i] = P[i];
SS_BEST.f = f;
non_improve = 0;
time_to_target = current_time;
}
else if (f == f_best)
non_improve++;
}
else non_improve++;
}
for (int i = 0;i < M;i++) Sol[i] = SS_BEST.S[i];
for (int i = 0;i < NM;i++) NSol[i] = SS_BEST.NS[i];
for (int i = 0;i < N;i++) P[i] = SS_BEST.P[i];
*F = f_best;
}
结果如图:
欲下载本文相关代码,请移步留言区
参考论文:
Xiangjing Lai,Dong Yue,Jin-Kao Hao,Fred Glover "Solution-based tabu search for the maximum min-sum dispersion problem." Information Sciences 441 (2018) 79-94.
论文第一作者简介:
Xiangjing Lai(赖向京),南京邮电大学研究员,硕士生导师,2012年6月于华中科技大学计算机学院获博士学位,师从黄文奇教授;2013年10月至2015年10月于法国昂热大学(University of Angers, France)从事博士后研究工作,师从法国大学研究院院士、法国国家特级教授 Jin-Kao Hao 博士;自2014年起,与冯诺依曼理论奖获得者、美国工程院院士、禁忌搜索算法提出者 Fred Glover 博士进行合作研究;2018年入选江苏省“六大人才高峰”高层次人才。
文案&排版:朱正雄(华中科技大学管理学院本科一年级)
指导老师:秦虎老师(华中科技大学管理学院)
审稿:周航(华中科技大学管理学院本科二年级)
如对文中内容有疑问,欢迎交流。PS:部分资料来自网络。
如有需求,可以联系:
秦虎老师(华中科技大学管理学院:professor.qin@qq.com)
朱正雄(华中科技大学管理学院本科一年级:2627889552@qq.com)
周航(华中科技大学管理学院本科二年级:zh20010728@126.com)