【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例

01 什么是禁忌搜索算法?

1.1 先从爬山算法说起

爬山算法从当前的节点开始,和周围的邻居节点的值进行比较。 如果当前节点是最大的,那么返回当前节点,作为最大值 (既山峰最高点);反之就用最高的邻居节点来,替换当前节点,从而实现向山峰的高处攀爬的目的。如此循环直到达到最高点。因为不是全面搜索,所以结果可能不是最佳。

1.2 再到局部搜索算法

局部搜索算法是从爬山法改进而来的。局部搜索算法的基本思想:在搜索过程中,始终选择当前点的邻居中与离目标最近者的方向搜索。同样,局部搜索得到的解不一定是最优解。

1.3 然后到禁忌搜索算法

为了找到“全局最优解”,就不应该执着于某一个特定的区域。于是人们对局部搜索进行了改进,得出了禁忌搜索算法。

禁忌(Tabu Search)算法是一种亚启发式(meta-heuristic)随机搜索算法,它从一个初始可行解出发,选择一系列的特定搜索方向(移动)作为试探,选择实现让特定的目标函数值变化最多的移动。为了避免陷入局部最优解,TS搜索中采用了一种灵活的“记忆”技术,对已经进行的优化过程进行记录和选择,指导下一步的搜索方向,这就是Tabu表的建立。

1.4 最后打个比方

为了找出地球上最高的山,一群有志气的兔子们开始想办法。
1) 爬山算法
兔子朝着比现在高的地方跳去。他们找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山法,它不能保证局部最优值就是全局最优值。

2) 禁忌搜索算法
兔子们知道一个兔的力量是渺小的。他们互相转告着,哪里的山已经找过,并且找过的每一座山他们都留下一只兔子做记号。他们制定了下一步去哪里寻找的策略。这就是禁忌搜索。

02 思想和过程

2.1 基本思想

标记已经解得的局部最优解或求解过程,并在进一步的迭代中避开这些局部最优解或求解过程。局部搜索的缺点在于,太过于对某一局部区域以及其邻域的搜索,导致一叶障目。为了找到全局最优解,禁忌搜索就是对于找到的一部分局部最优解,有意识地避开它,从而或得更多的搜索区域。

比喻:兔子们找到了泰山,它们之中的一只就会留守在这里,其他的再去别的地方寻找。就这样,一大圈后,把找到的几个山峰一比较,珠穆朗玛峰脱颖而出。

2.2 算法过程

step1:给以禁忌表H=空集,并选定一个初始解xnow;

step2:满足停止规则时,停止计算,输出结果;否则,在xnow的邻域N(xnow)中选择不受禁忌的候选集Can_N(xnow);在Can_N(xnow)中选一个评价值最佳的解xnext,xnow=xnext;更新历史记录H,保存f(xnow),重复step2;

step3:在保存的众多f中,挑选最小(大)值作为解;

03 相关概念解释

又到了科普时间了。其实,关于邻域的概念前面的好多博文都介绍过了。今天还是给大家介绍一下。这些概念对理解整个算法的意义很大,希望大家好好理解。

1) 邻域
官方一点:所谓邻域,简单的说即是给定点附近其他点的集合。在距离空间中,邻域一般被定义为以给定点为圆心的一个圆;而在组合优化问题中,邻域一般定义为由给定转化规则对给定的问题域上每结点进行转化所得到的问题域上结点的集合。
通俗一点:邻域就是指对当前解进行一个操作(这个操作可以称之为邻域动作)可以得到的所有解的集合。那么邻域的本质区别就在于邻域动作的不同了。

2) 邻域动作
邻域动作是一个函数,通过这个函数,对当前解s,产生其相应的邻居解集合。例如:对于一个bool型问题,其当前解为:s = 1001,当将邻域动作定义为翻转其中一个bit时,得到的邻居解的集合N(s)={0001,1101,1011,1000},其中N(s) ∈ S。同理,当将邻域动作定义为互换相邻bit时,得到的邻居解的集合N(s)={0101,1001,1010}。

3) 禁忌表
包括禁忌对象和禁忌长度。(当兔子们再寻找的时候,一般地会有意识地避开泰山,因为他们知道,这里已经找过,并且有一只兔子在那里看着了。这就是禁忌搜索中“禁忌表(tabu list)”的含义。)

4) 侯选集合
侯选集合由邻域中的邻居组成。常规的方法是从邻域中选择若干个目标值或评价值最佳的邻居入选。

5) 禁忌对象
禁忌算法中,由于我们要避免一些操作的重复进行,就要将一些元素放到禁忌表中以禁止对这些元素进行操作,这些元素就是我们指的禁忌对象。(当兔子们再寻找的时候,一般地会有意识地避开泰山,因为这里找过了。并且还有一只兔子在这留守。)

6) 禁忌长度
禁忌长度是被禁对象不允许选取的迭代次数。一般是给被禁对象x一个数(禁忌长度) t ,要求对象x 在t 步迭代内被禁,在禁忌表中采用tabu(x)=t记忆,每迭代一步,该项指标做运算tabu(x)=t−1,直到tabu(x)=0时解禁。于是,我们可将所有元素分成两类,被禁元素和自由元素。禁忌长度t 的选取可以有多种方法,例如t=常数,或t=[√n],其中n为邻域中邻居的个数;这种规则容易在算法中实现。
(那只留在泰山的兔子一般不会就安家在那里了,它会在一定时间后重新回到找最高峰的大军,因为这个时候已经有了许多新的消息,泰山毕竟也有一个不错的高度,需要重新考虑,这个归队时间,在禁忌搜索里面叫做“禁忌长度(tabu length)”。)

7) 评价函数
评价函数是侯选集合元素选取的一个评价公式,侯选集合的元素通过评价函数值来选取。以目标函数作为评价函数是比较容易理解的。目标值是一个非常直观的指标,但有时为了方便或易于计算,会采用其他函数来取代目标函数。

8) 特赦规则
在禁忌搜索算法的迭代过程中,会出现侯选集中的全部对象都被禁忌,或有一对象被禁,但若解禁则其目标值将有非常大的下降情况。在这样的情况下,为了达到全局最优,我们会让一些禁忌对象重新可选。这种方法称为特赦,相应的规则称为特赦规则。
(如果在搜索的过程中,留守泰山的兔子还没有归队,但是找到的地方全是华北平原等比较低的地方,兔子们就不得不再次考虑选中泰山,也就是说,当一个有兔子留守的地方优越性太突出,超过了“best so far”的状态,就可以不顾及有没有兔子留守,都把这个地方考虑进来,这就叫“特赦准则(aspiration criterion)”。)

04 代码实例(代码来源网络)

这次还是用一个求解TSP的代码实例来给大家讲解吧。

数据文件下载戳这里:
http://www.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/tsp/
下载下来跟代码放一个路径里直接就可以跑,记得把下面那个存路径的string改成你自己的。输入是0~9代表10个不同的tsp文件。

  1#include <iostream>
2#include <fstream>
3#include <string>
4#include <algorithm>
5#include <cstdlib>
6#include <climits>
7#include <ctime>
8#include <list>
9using namespace std;
10
11#define TABU_SIZE 10    //禁忌代数 
12#define SWAPSIZE 5      //对于每个点,都只选与它距离较小的前SWAPSIZE个与它交换 
13#define ITERATIONS 100
14#define INF INT_MAX
15int rowIndex; 
16double adj[60][60];
17int ordered[60][60];
18int city1[60], city2[60], path[60];
19string filename[10] = {"gr17.tsp""gr21.tsp""gr24.tsp""fri26.tsp""bayg29.tsp""bays29.tsp""swiss42.tsp""gr48.tsp""hk48.tsp""brazil58.tsp"};
20int bestans[10] = {20852707127293716102020127350461146125395}; 
21int bestIteration;
22int tabuList[2000][4];
23
24
25bool cmp(int a, int b);
26double TabuSearch(const int & N);
27double GetPathLen(int* city, const int & N);
28
29int main(){
30    string absolute("C:\\");
31    int CASE;
32    srand(time(0));
33    while (cin >> CASE && CASE < 10 && CASE > -1){
34        memset(adj, 0sizeof(adj));
35        memset(city1, 0sizeof(city1));
36        memset(city2, 0sizeof(city2));
37        memset(tabuList, 0sizeof(tabuList));
38        memset(path, 0sizeof(path));
39
40        string relative = filename[CASE];
41        string filepath = absolute+relative;
42        ifstream infile(filepath.c_str());
43        if (infile.fail()){
44            cout << "Open failed!\n";
45        }
46        int n;
47        infile >> n;
48        for (int j = 0; j < n; j++){
49            for (int k = 0; k < n; k++){
50                infile >> adj[j][k];
51            }
52        }
53
54        clock_t start, end;
55        start = clock();
56        int distance = TabuSearch(n);
57        end = clock();
58        double costTime = (end - start)*1.0/CLOCKS_PER_SEC;
59        cout << "TSP file: " << filename[CASE] << endl;
60        cout << "Optimal Soluton: " << bestans[CASE] << endl;
61        cout << "Minimal distance: " << distance << endl;
62        cout << "Error: " << (distance - bestans[CASE]) * 100 / bestans[CASE] << "%" << endl;
63        cout << "Best iterations:  " << bestIteration << endl;
64        cout << "Cost time:        " << costTime << endl
65        cout << "Path:\n";
66        for (int i = 0; i < n; i++){
67            cout << path[i] + 1 << " ";
68        }
69        cout << endl << endl;;
70        infile.close();
71    }
72    return 0;
73}
74
75
76//生成随机的城市序列
77void CreateRandOrder(int* city, const int & N){
78    for (int i = 0; i < N; i++){
79        city[i] = rand() % N;
80        for (int j = 0; j < i; j++){
81            if (city[i] == city[j]){
82                i--;
83                break;
84            }
85        }
86    }
87}
88
89
90double GetPathLen(int* city, const int & N){
91    double res = adj[city[N-1]][city[0]];
92    int i;
93    for (i = 1; i < N; i++){
94        res += adj[city[i]][city[i-1]];
95    }
96    return res;
97}
98
99
100void UpdateTabuList(int len){
101    for (int i = 0; i < len; i++){
102        if (tabuList[i][3] > 0)
103            tabuList[i][3]--;
104    }
105}
106
107
108double TabuSearch(const int & N){
109    int countI, countN, NEIGHBOUR_SIZE = N * (N - 1) / 2;
110    double bestDis, curDis, tmpDis, finalDis = INF;
111    bestIteration = 0
112    string bestCode, curCode, tmpCode;
113
114    //预生成所有可能的邻域,0、1两列是要交换的点,第2列是这种交换下的路径长度,第3列是禁忌长度 
115    int i = 0;
116    for (int j = 0; j < N - 1; j++){
117        for (int k = j + 1; k < N; k++){
118            tabuList[i][0] = j;
119            tabuList[i][1] = k;
120            tabuList[i][2] = INF;
121            i++;
122        }
123    }
124
125
126    //生成初始解,25次用于跳出局部最优 
127    for (int t = 0; t < 25; t++){
128        CreateRandOrder(city1, N);
129        bestDis = GetPathLen(city1, N);
130
131    //开始求解 
132    //迭代次数为ITERATIONS 
133        countI = ITERATIONS;
134        int a, b;
135        int pardon[2], curBest[2];
136        while (countI--){
137            countN = NEIGHBOUR_SIZE;
138            pardon[0] = pardon[1] = curBest[0] = curBest[1] = INF;
139            memcpy(city2, city1, sizeof(city2));
140            //每次迭代搜索的邻域范围为NEIGHBOUR_SIZE 
141            while (countN--){
142                //交换邻域 
143                a = tabuList[countN][0];
144                b = tabuList[countN][1];
145                swap(city2[a], city2[b]);
146                tmpDis = GetPathLen(city2, N);
147                //如果新的解在禁忌表中,就只存特赦相关信息 
148                if (tabuList[countN][3] > 0){ 
149                    tabuList[countN][2] = INF; 
150                    if (tmpDis < pardon[1]){
151                        pardon[0] = countN;
152                        pardon[1] = tmpDis;
153                    }
154                }
155                //否则,把距离存起来 
156                else {
157                    tabuList[countN][2] = tmpDis;
158                }
159                swap(city2[a], city2[b]);//再换回去回复原状方便后面使用   
160            }
161            //遍历邻域求得此代最佳 
162            for (int i = 0; i < NEIGHBOUR_SIZE; i++){
163                if (tabuList[i][3] == 0 && tabuList[i][2] < curBest[1]){
164                    curBest[0] = i;
165                    curBest[1] = tabuList[i][2];
166                }
167            }
168            //特赦的 
169            if (curBest[0] == INF || pardon[1] < bestDis) {
170                curBest[0] = pardon[0];
171                curBest[1] = pardon[1];
172            }
173
174            //更新此代最优
175            if (curBest[1] < bestDis){
176                bestDis = curBest[1];
177                tabuList[curBest[0]][3] = TABU_SIZE;
178                bestIteration = ITERATIONS - countI;
179                a = tabuList[curBest[0]][0];
180                b = tabuList[curBest[0]][1];
181                swap(city1[a], city1[b]);
182            }
183            UpdateTabuList(NEIGHBOUR_SIZE);
184        }
185        //更新全局最优
186        if (bestDis < finalDis){
187            finalDis = bestDis;
188            memcpy(path, city1, sizeof(path));
189        }
190    }
191    return finalDis;
192}

欲获取代码,请关注我们的微信公众号【程序猿声】,在后台回复:TS代码。即可获取。

微信公众号微信公众号

转载于:https://www.cnblogs.com/dengfaheng/p/9737556.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/426678.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

14. Java基础之泛型

一. 泛型概念的提出&#xff08;为什么需要泛型&#xff09;&#xff1f; 首先&#xff0c;我们看下下面这段简短的代码: 1 public class GenericTest {2 3 public static void main(String[] args) {4 List list new ArrayList();5 list.add("qqyum…

java学习(15):巩固练习

//任务 1 //编写控制台java程序&#xff0c;使用Scanner 对象相关方法从 //控制台接收用户输入如下数据并使用相关的局部变量接收&#xff0c;在控制台打印输出。 //老师的姓名&#xff1b;老师的性别&#xff1b;老师的工资&#xff1b;老师的年龄&#xff1b;工作时长 import…

java学习(16):巩固练习

/任务 2 编写控制台java程序&#xff0c;将以下数据使用合理类型变量进行接收赋值 3.5&#xff1b;185.59&#xff1b;8500.50 要求在控制台打印这些数据并只显示整数部分。/ import java.util.Scanner; public class test02{ public static void main(String[] args){ Scanner…

mac电脑投屏到小米盒子_苹果手机搜不到小米盒子怎么办?

刚买的小米电视盒子迫不及待想投屏&#xff0c;但是手机是苹果系统&#xff0c;都是连得同一wifi&#xff0c;可是手机就是搜索不到小米家的客厅电视&#xff0c;这种情况该怎么办呢&#xff1f;以下小编给大家详细介绍了苹果手机搜不到小米盒子该怎么办。苹果设备中搜不到小米…

Redis实现之对象(三)

集合对象 集合对象的编码可以是intset或者hashtable&#xff0c;intset编码的集合对象使用整数集合作为底层实现&#xff0c;集合对象包含的所有元素都被保存在整数集合里面。举个栗子&#xff0c;以下代码将创建一个图1-12所示的intset编码集合对象&#xff1a; 127.0.0.1:637…

java学习(17):巩固练习

//#任务 3 //#已知有三个人&#xff0c;张无忌&#xff0c;任盈盈&#xff0c;任我行。当前只知道任盈盈的年龄 //#可以被用户从控制台输入&#xff0c;并且用户可以告知任盈盈的年龄比张无忌的年龄小几岁&#xff0c; //#任我行年龄是张无忌和任盈盈年龄和还要大几岁&#xff…

mysql innodb redolog_MySQL · 引擎特性 · InnoDB redo log漫游(转)

前言InnoDB 有两块非常重要的日志&#xff0c;一个是undo log&#xff0c;另外一个是redo log&#xff0c;前者用来保证事务的原子性以及InnoDB的MVCC&#xff0c;后者用来保证事务的持久性。和大多数关系型数据库一样&#xff0c;InnoDB记录了对数据文件的物理更改&#xff0c…

Jenkins配置:添加用户和管理权限

Jenkins配置&#xff1a;添加用户和管理权限 参考文章&#xff1a;http://www.cnblogs.com/zz0412/p/jenkins_jj_14.html 今天给大家说说使用Jenkins专有用户数据库的配置&#xff0c;和一些常用的权限配置。 配置用户注册 在新安装好的jenkins中&#xff0c;默认是没有设置用户…

java学习(18):巩固练习

/任务 4 白大壮和白二壮是双胞胎兄弟&#xff0c; 白大壮的身高增加1厘米正好是白二壮的身高 &#xff0c;白二壮体重正好是妹妹白无瑕体重&#xff0c;编写程序完成白大壮 和白二壮身高的计算并输出&#xff0c;并计算白无瑕的体重输出是多少/ import java.util.Scanner; publ…

java-appium-527进阶-1 UiAutomator12区别和封装

1.UiAutomator和UiAtumator2的区别&#xff1a; 1.1 UiAutomator1有关于id定位的策略 UiAutomator1 id定位在resourceid匹配失败时&#xff0c;会匹配contentDesc。 安卓会根据id进行3种情况的判断&#xff1a; 1.resourceId 如user_profile_icon2.accessibility id3.Strings.…

java学习(19):巩固练习

/任务 5 有三位老师&#xff0c;王老师&#xff0c;孙老师和小李老师&#xff0c; 王老师工龄最长(15年)&#xff0c;孙老师工龄比王老师小3年&#xff0c; 小李老师工龄最短&#xff0c;是王老师和孙老师工龄和的二分之一 再除以2的余数正好是他的工龄&#xff0c;编写程序从控…

mysql分组获取其他字段_sqlserver group by后获取其他字段(多种方法)

大家都知道用group by的话&#xff0c;select 后面指定的字段必须与group by后面的一致。group by 只有个别字段&#xff0c;如果拿出其他未分组的字段信息呢&#xff1f;在网上搜了下&#xff0c;总结如下&#xff1a;使用了group by 之后&#xff0c;就要求select后面的字段包…

搜索引擎基础概念(1)—— 倒排索引

“ 吾有三剑&#xff0c;唯子所择&#xff1b;皆不能杀人&#xff0c;且先言其状。一曰含光&#xff0c;视之不可见&#xff0c;运之不知有。其所触也&#xff0c;泯然无际&#xff0c;经物而物不觉。二曰承影&#xff0c;将旦昧爽之交&#xff0c;日夕昏明之际&#xff0c;北面…

java学习(20):巩固练习

//用运算符判断2019是不是闰年 /①、普通年能被4整除且不能被100整除的为闰年。 &#xff08;如2004年就是闰年,1901年不是闰年&#xff09;地球公转示意图②、世纪年能被400整除的是闰年。 (如2000年是闰年&#xff0c;1900年不是闰年) ③、 对于数值很大的年份能整除3200,但同…

汇编软件的安装与实验一

软件的安装在课程邮箱里有详尽的介绍&#xff0c;但在安装调试的时候还是出了一点小问题&#xff0c;创建虚拟盘符的时候&#xff0c;我将masm文件夹前面套上了一层名为masm文件夹&#xff0c;导致虚拟盘符创建之后无法使用debug。 随后就是实验 实验1.1写入程序段并且执行 a命…

upc 9519 New Game

New Game 时间限制: 1 Sec 内存限制: 128 MB Special Judge提交: 157 解决: 53[提交] [状态] [讨论版] [命题人:admin]题目描述 Eagle Jump公司正在开发一款新的游戏。泷本一二三作为其员工&#xff0c;获得了提前试玩的机会。现在她正在试图通过一个迷宫。这个迷宫有一些特…

java学习(21):移位运算符

//移位运算符 public class test{ public static void main(String[] args){ int num3; //向左移位 System.out.println(“移位之前的二进制为”Integer.toBinaryString(num)); int moveleftnum<<2; System.out.println(“移位之后的值为”moveleft); //向右移位 int num…

Datagridview绘制

#region 绘制private void dataGridView_main_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e){int status_column_index 14;//会诊状态所在列DataGridViewRow row dataGridView_main.Rows[e.RowIndex];//获取行DataGridViewCell cell row.Cells[15];//按钮…

java学习(22):if语句

/任务 1&#xff1a;if语句 编写控制台java程序&#xff0c;使用Scanner 对象相关方法从控制台接收用户输入学生年龄&#xff0c; 如果输入的年龄大于18&#xff0c;则输出“你是一个成年人了&#xff0c;该有担当了&#xff01;/ import java.util.Scanner; public class test…

java学习(23):if..else

/任务2&#xff1a;if else 语句 编写控制台java程序&#xff0c;模拟银行取款的功能。 使用Scanner对象相关方法从控制台接收用户输入的银行卡账号和密码&#xff0c; 与预先定义好的银行卡账号密码相同则输出用户名密码正确&#xff0c;可以取款&#xff1b;如果账号或者密码…