• C++下基于模拟退火算法解决TSP问题


    一、原理

    首先明确TSP问题的目标是什么,假设当前有3个城市,需要你从第一个城市开始,遍历所有城市,然后回到初始位置,要求总路径最短。这个时候就需要计算每个城市之间的两两距离,然后按顺序确定一条最短路径。

    最稳妥的方案是,使用暴力枚举,列举出来所有的路线为1->2->3->1、1->3->2->1,然后计算一个最短路径即可。

    但是当城市数量很多的时候,暴力枚举的方法计算量十分大,此时就需要考虑有没有简单一些的方法,虽然不是十分准确,但是能猜出一个大致的结果,即使不是最优解,只要接近最优结果也能接受。其他大部分智能优化算法都是这种思路,只是使用的具体思路有区别。

    模拟退火算法,实际就是猜答案,然后从猜的结果中找个最优的结果输出,当猜的数量够大,结果就最接近实际结果。它会设置一个初始温度比较高例如1000,一个结束温度20,以一定函数从1000降低到20,然后设置一个迭代次数,每次迭代都会随机打乱路线,如果这个路线比之前的路线好,则直接把它作为最优路线,如果没有之前的好,则以一个很小的概率接受,这个概率也是通过随机数确定的,这里为什么要以一个很小的概率接受一个不好的结果呢?其实是为了防止结果陷入局部最优解,但是对于模拟退火这种猜答案的方式,个人感觉其实用不着以很小的概率接受一个不好的路线。

    所以这个算法名字听起来不知所云,但是主要就是猜答案,也没有什么特殊的。

    二、代码

    1. //模拟退火算法(SA)求解TSP问题
    2. /*类的体会2:
    3. 1.类外访问类中private方式:
    4. ①通过设置类中的public,get()函数,来获取一些变量值;
    5. ②对于数组的获取,一方面可以通过get(下标)函数来完成一对一的获取;
    6. 另一方面可以通过在类中public处声明类外的友元函数friend(对象),友元函数通过对象可以访问到private
    7. 2.类中函数要访问类外数据:
    8. ①全局变量可以直接访问;
    9. ②在类外创建该类的对象,通过对象调用函数,并传递类外数据参数;
    10. ③访问其他类还可以用友元函数
    11. */
    12. #include
    13. #include
    14. #include
    15. #include
    16. #include
    17. #include
    18. using namespace std;
    19. #define T0 1000 //初始温度
    20. #define T_end 20 //退出温度(环境温度)
    21. #define q 0.9 //退火系数
    22. #define L 10 //每个温度时的迭代次数,即链长
    23. #define C 10 //城市数量
    24. //10个城市的坐标:
    25. double citys_position[C][2] =
    26. {
    27. {1304,2312},{3639,1315},{4177,2244},{3712,1399},{3488,1535},
    28. {3326,1556},{3238,1229},{4196,1004},{4312,7900},{4386,570}
    29. };
    30. class Simulated_Annealing
    31. {
    32. private:
    33. int i, j;
    34. int Current_Solution[C]; //存放一个解
    35. int Current_copy[C]; //存放解的副本,用于计算差异
    36. double T = T0; //温度变量,并初始化
    37. double f1, f2, df; //f1是初始解目标值,f2是新解目标值,df是新旧解目标值差
    38. int count = 0; //记录降温次数(因为按系数退火,迭代次数不确定)
    39. vectordouble>> All_soluitons; //存放每代中最后的解(也可以是全部解,加上链表迭代的)
    40. vector<double> All_fitness; //存放每代中最后的解值
    41. public:
    42. /*《函数SA声明》:模拟退火主体功能*/
    43. void SA();
    44. /*《函数citys_generate声明》:初始化解*/
    45. void citys_generate();
    46. /*《函数Swap声明》:初始化解*/
    47. void Swap(int* input_solution);
    48. /*《Fitness函数声明:对传入解计算适应度值》*/
    49. double Fitness(int* input_solution);
    50. /*《distance函数声明:计算两两节点的距离,传入两个城市各自的坐标信息》*/
    51. double distance(double* city1, double* city2);
    52. /*《getCount函数:给外部调用count值》*/
    53. int getCount() { return count; }
    54. /*《getF1函数:给外部调用f1值》*/
    55. int getF1() { return f1; }
    56. /*《getCurrent_Solution函数:给外部调用该数组》*/
    57. int getCurrent_Solution(int k)
    58. {
    59. return Current_Solution[k];
    60. }
    61. /*《get_All_solutions函数申明:定义为类中的友元函数,可以通过对象去访问类中的private。
    62. 注意:友元函数是可以访问类中的私有成员,但是得告诉他,他是哪个类的朋友。
    63. 是通过对象的传参来作过渡,而不能实现直接的访问,他找不到!》*/
    64. friend void get_All_solutions(Simulated_Annealing &SA);
    65. };
    66. void main()
    67. {
    68. srand((unsigned)time(NULL));
    69. time_t start, finish; //计时变量
    70. start = clock(); //程序运行开始计时
    71. /*《调用Simulated_Annealing类》*/
    72. Simulated_Annealing SA1;
    73. SA1.SA();
    74. finish = clock(); //程序结束停止计时
    75. //计算运行时间
    76. double run_time = ((double)(finish - start)) / CLOCKS_PER_SEC;
    77. //输出结果
    78. printf("模拟退火算法解TSP问题:\n");
    79. cout << "初始温度T0=" << T0 << ",降温系数q=" << q << endl;
    80. printf("每个温度迭代%d次,共降温%d次。\n", L, SA1.getCount());
    81. /*《调用get_All_solutions函数:输出每次退火的方法和值,友元函数,可以通过对象去访问类中的private》*/
    82. get_All_solutions(SA1);
    83. printf("最终得TSP问题最优路径为:\n");
    84. for (int j = 0; j < C - 1; j++)
    85. {
    86. printf("%d—>", SA1.getCurrent_Solution(j));
    87. }
    88. printf("%d—>", SA1.getCurrent_Solution(C - 1));
    89. printf("%d\n", SA1.getCurrent_Solution(0));
    90. cout << "最优路径长度为:" << SA1.getF1() << endl;
    91. printf("程序运行耗时%1f秒.\n", run_time);
    92. }
    93. /*《函数SA定义》:模拟退火主体功能*/
    94. void Simulated_Annealing::SA()
    95. {
    96. /*①《调用函数citys_generate》:初始化解*/
    97. citys_generate();
    98. /*《调用Fitness函数:对传入解计算适应度值》*/
    99. f1 = Fitness(Current_Solution);//随机求解的一个代价值
    100. while (T > T_end)//当温度低于结束温度时,退火结束
    101. {
    102. All_soluitons.push_back({}); //每代开始添加一个空行,存放本代结果值
    103. All_fitness.push_back({}); //每代开始添加一个空行,存放本代代价值
    104. for (i = 0; i < L; i++)//迭代次数5
    105. {
    106. //②复制初始解
    107. for (j = 0; j < C; j++)
    108. {
    109. Current_copy[j] = Current_Solution[j];
    110. }
    111. /*③《调用Swap函数:对传入解做邻域搜索,两点交换》*/
    112. Swap(Current_copy);//随机生成一个新的路线
    113. /*④《调用Fitness函数:对传入解计算适应度值》*/
    114. f2 = Fitness(Current_copy);//计算当前代价
    115. df = f1 - f2;//当前代价和前一个代价差
    116. /*退火原理:Metropolis准则,df<0*/
    117. //⑤若新解>旧解值,df<0,则以概率 exp(df / T) 接受新解 df越大,指数越大。含义为新解效果越差,接受概率越小
    118. if (df < 0)
    119. {
    120. //随机数(0,1),用于决定是否接受新解
    121. double r;
    122. r = (double)rand() / RAND_MAX; //分母32767
    123. if (r < exp(df / T))//以很小的概率接受这个不好的解
    124. {
    125. //接受新解
    126. for (j = 0; j < C; j++)
    127. {
    128. Current_Solution[j] = Current_copy[j];
    129. }
    130. f1 = f2;
    131. }
    132. }
    133. //⑥若新解<=旧解值,df>=0,则直接接受新解
    134. else//如果新解效果比较好,直接用这个新解
    135. {
    136. for (j = 0; j < C; j++)
    137. {
    138. Current_Solution[j] = Current_copy[j];
    139. }
    140. f1 = f2;
    141. }
    142. }
    143. //⑦存放本次迭代的最后方案和适应值
    144. for (int j = 0; j < C; j++)
    145. {
    146. All_soluitons[count].push_back(Current_Solution[j]);//每次迭代的路径存入vector
    147. }
    148. All_fitness[count] = f1;
    149. //⑧以一定的速率降温
    150. T *= q;//以1/2的速率减小
    151. count++;
    152. }
    153. }
    154. /*《函数citys_generate定义》:初始化解*/
    155. void Simulated_Annealing::citys_generate()
    156. {
    157. //①一个城市序列
    158. vector<int> temp_city;
    159. for (int i = 0; i < C; i++)//城市数量
    160. {
    161. temp_city.push_back(i + 1);
    162. }
    163. //②打乱
    164. random_shuffle(temp_city.begin(), temp_city.end());//均匀分布来随机移动元素
    165. //③赋值给Current_Solution
    166. for (int j = 0; j < temp_city.size(); j++)
    167. {
    168. Current_Solution[j] = temp_city[j];//初始为随机解
    169. }
    170. }
    171. /*《函数Swap定义》:初始化解*/
    172. void Simulated_Annealing::Swap(int* input_solution)
    173. {
    174. //①生成交换节点下标
    175. int point1 = rand() % C;//交换点1
    176. int point2 = rand() % C;//交换点2
    177. while (point1 == point2)
    178. {
    179. point2 = rand() % C;//保证交换点不同
    180. }
    181. //②交换
    182. swap(input_solution[point1], input_solution[point2]);//随机交换最终路径的航点
    183. }
    184. /*《Fitness函数定义:对传入解计算适应度值》*/
    185. double Simulated_Annealing::Fitness(int* input_solution)//代价函数
    186. {
    187. //初始化路径长度
    188. double cost = 0;
    189. //从第一个城市遍历到最后一个城市
    190. for (int j = 0; j < C - 1; j++)
    191. {
    192. /*《调用distance函数:计算两两节点的距离,传入两个城市各自的坐标信息》*/
    193. int k = input_solution[j];//城市序号-1=城市位置下标 第二个参数为第二个城市的位置
    194. cost += distance(citys_position[input_solution[j] - 1], citys_position[input_solution[j + 1] - 1]);//初始位置为第一个城市
    195. }
    196. //从最后一个城市回到第一个城市
    197. cost += distance(citys_position[input_solution[C - 1] - 1], citys_position[input_solution[0] - 1]);
    198. return cost;
    199. }
    200. /*《distance函数声明:计算两两节点的距离,传入两个城市各自的坐标信息》*/
    201. double Simulated_Annealing::distance(double* city1, double* city2)
    202. {
    203. /*计算相邻两城市间距离*/
    204. double x1 = city1[0]; //城市1横坐标
    205. double y1 = city1[1];
    206. double x2 = city2[0];
    207. double y2 = city2[1]; //城市2纵坐标
    208. double dist = pow((pow((x1 - x2), 2) + pow((y1 - y2), 2)), 0.5);
    209. return dist; //返回距离值
    210. }
    211. /*《get_All_solutions函数申明:定义为类中的友元函数,通过对象可以访问类中的向量》*/
    212. void get_All_solutions(Simulated_Annealing &SA)
    213. {
    214. cout << "每次退火内部迭代的最终解:" << endl;
    215. for (int i = 0; i < SA.All_soluitons.size(); i++)
    216. {
    217. for (int j = 0; j < SA.All_soluitons[i].size(); j++)
    218. {
    219. cout << SA.All_soluitons[i][j] << "—>";
    220. }
    221. cout << SA.All_soluitons[i][0] << endl;
    222. cout << "路线代价为:" << SA.All_fitness[i] << std::endl << std::endl;
    223. }
    224. }

  • 相关阅读:
    Flink技术灵活使用总结(一)状态与状态后端
    【无标题】
    华封科技举办2022年首场技术认证培训
    第七章练习题
    2023成都.NET线下技术沙龙圆满结束
    day-63 代码随想录算法训练营(19) 图论 part 02
    基于预训练模型的Unet【超级简单】【懒人版】【Pytorch版】
    SQL与关系数据库基本操作
    阿里云数据库MongoDB恢复到本地
    雅思写作拾遗01----图表类作文
  • 原文地址:https://blog.csdn.net/ljjjjjjjjjjj/article/details/132842885