多线程动态规划算法求解TSP(Traveling Salesman Problem) 并附C语言实现例程
TSP问题描述:

TSP问题的动态规划解法:
引用一下这篇文章,觉得作者把动态规划算法讲的非常明白:https://blog.csdn.net/derek_tian/article/details/45057443
动态规划算法的并行:
终于到了文章的重点,如何高效率的将主问题拆开并分配给各个线程运算是值得研究的话题。这里先引用动态规划算法的示意图:

(图2)
图中所示的是城市数为4的问题,从第一行到第二行,把问题拆分成了三个子问题,这三个子问题互不影响互不相关。将这三个子问题再一次拆分,每个问题又能拆出两个问题。将这个规律推广到n,城市数为n的问题通过第一次拆分可以变为n-1个子问题,再拆分一次共可以变为(n-1)*(n-2)个子问题。
有了能把问题拆成互不相干的子问题作为前提,在并行时就并不需为数据频繁的加锁解锁,能比较顺利地完成并行的操作了。接下来是具体的实现手段:
首先在全局维护唯一一个二维表,所有子线程的读取与修改都是在这一张表上进行的。例程中就是dp变量。
接下来定义一个函数,用来求解每个子问题,这个时候出现了另一个问题:怎么样表示一个子问题。在动态规划算法中有一个状态压缩的概念,即为用一段二进制码表示城市的集合,例如共五个城市时表示{1, 3, 4}这个集合即为二进制"11010"。相同的思路,对于d(1,{2,3})问题,我们给函数输入起点,以及目标的集合,就可以完整的表示问题。函数内选择的是递归算法,因为非递归算法很难保证子问题互不相关(可能也只是我懒得想,有思路的同学可以在评论区中讨论一下)。具体的动态规划递归实现算法很多文章中都有,我就不过多赘述了。下面是这一部分的源代码:
*注意:我在写这个函数的时候规定的setMask是包含了起点城市的,也就是对于d(1,{2,3})这个问题传入函数的参数的是(1,0b"1110")。
int TSP( int x, int setMask ) {
int mask, i, minimum;
if ( dp[ setMask ][ x ] != - )
return dp[ setMask ][ x ]; /* if already have value */
mask = << x;
dp[ setMask ][ x ] = 1e9; /* set infinite */
for ( i = ; i < n; ++i ) {
if ( i != x && ( setMask & ( << i ) ) && map[ i ][ x ] != ) { /* if have path */
minimum = TSP( i, setMask - mask ) + map[ i ][ x ] ;
dp[ setMask ][ x ] = min( dp[ setMask ][ x ], minimum);
}
}
return dp[ setMask ][ x ];
}
现在我们有了表达子问题的方法,就该开始解决具体怎么拆开问题的算法了。问题的要求中说明了最大可以创建的线程数,也就是说为了保证尽可能高的效率,程序中应该将问题拆到数量刚好大过线程数的情况。
当我们计算出了应该拆分的层数后,就该为产生的一大波子问题生成描述了。
有了之前定义子问题动态规划的算法为基础,其实可以发现,对于同一个setMask,其产生的同辈问题数是很容易得出来的。就拿刚刚的例子d(1,{2,3})来说,它的setMask是"1110",起点是1号城市。然而,对于与这个问题互为同辈关系的d(2,{1,3}),d(3,{1,3})问题来说,他们的setMask也都是"1110",只不过起点依次是2和3号城市。所以只要生成出了这个集合,其对应的问题都能很容易表示,这也就把工作的重心转义到了如何生成这个集合,也就是例程中是setMask。
对于从同一个整道题求解的问题分离出来的子问题来说,其城市集合其实是一个组合数。我们还选取图2作为例子,现在经过计算后,我们需要把主问题拆分两层,即拆到d(2,{3})这一层。我们列出所有的集合,分别是"1100", "1010", "0110",发现了吗,其实这些集合也就是剔除了起点城市以后,将两个1与一个0进行组合的所有情况。这个规律同样可以推广到n个城市,如果要将问题拆分k层,所有的问题集合即是固定集合中对应初始点(即程序输入的s)为0后将n-k-1个1与k-1个0的所有组合数。数学表示为:
*注:公式中的${S \choose n-k-1}$表示S集合的n-k-1排列。
$Let\ S=\{0,1,2,\cdots,n-1\}\backslash\{s\}
\\
Then\ let\ C= {S \choose n-k-1}
\\
For\ every\ i \in C, its\ setmask=\overbrace{00\cdots00}^{n}+(1<<i[0])+(1<<i[1])+\cdots+(1<<i[n-k-1])$
希望我讲清楚了里面的数学关系,如果觉得我讲的有问题或者看不懂的同学可以在评论区留言……以下是上述数学过程的c语言代码,为了逻辑更清晰我将计算组合数和求解所有组合数封成了factorial和setCombination函数:
while (t > nn) { /* let all thread have work to do */
nn *= (nn - );
nc += ; /* extends layers until t > nn */
}
j = ;
candiNum = malloc(sizeof(int) * (n - ));
for (i = ; i < n; ++i) { /* candiNum set means all cities exclude the start piont*/
if (i != s) {
candiNum[j] = i; /* exclude the start point */
j++;
}
}
combNum = factorial(nc, n - ); /* calculate combination number */
combSet = malloc(sizeof(int) * combNum);
combptr = combSet;
numOf1 = n - - nc; /* there should have `numOf1` of 1 in every item */
setCombination(candiNum, n - , numOf1);
接下来就是使用pthread创建线程来求解每一个子问题了。其中主函数作为调度线程来给0~t-1个线程依次分配任务。因为基本上可以认为每一个平行的子问题计算的时间是完全一样的,所以只需要先给0~t-1个线程分配0~t-1号任务,再等待到0号线程结束以后,为其分配t号任务,以此类推。当所有的子问题都被解决以后,dp这个二维表中已经有所有我们需要的子问题结论了。此时主问题的答案可以很快通过这些答案计算出来,也没有并行的必要了。最后提供整个程序的代码以及Makefile:
// file tsp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "tsp.h"
#include <time.h>
#define min(a,b) (((a)<(b))?(a):(b)) /* macro func of min */ time_t tm;
int ** map, ** dp, * combSet, * combptr, * posOf1, n; /* init vars */
trdArgs args[];
pthread_t tid[]; /* thread ids */ int TSP( int x, int setMask ) {
int mask, i, minimum;
if ( dp[ setMask ][ x ] != - )
return dp[ setMask ][ x ]; /* if already have value */
mask = << x;
dp[ setMask ][ x ] = 1e9; /* set infinite */
for ( i = ; i < n; ++i ) {
if ( i != x && ( setMask & ( << i ) ) && map[ i ][ x ] != ) { /* if have path */
minimum = TSP( i, setMask - mask ) + map[ i ][ x ] ;
dp[ setMask ][ x ] = min( dp[ setMask ][ x ], minimum);
}
}
return dp[ setMask ][ x ];
} void * trd(void * SArg) {
TSP((*(trdArgs *)SArg).startPoint, (*(trdArgs *)SArg).setMask);
return NULL;
} void setCombination(int arr[], int n, int r) { /* set all combination into combptr */
int * data = malloc(sizeof(int) * r); /* Temporary array to store current combination */
combinationUtil(arr, data, , n - , , r);
free(data); /* avoid leak */
} void combinationUtil(int arr[], int data[], int start, int end, int index, int r) {
int i, j, tmpi = ; /* init vars */
if (index == r) {
for (j = ; j < r; j++) /* add nums to binay */
tmpi |= ( << data[j]);
*combptr = tmpi;
combptr++; /* next cell in array */
return;
}
for (i = start; i <= end && end - i + >= r - index; i++) { /* recersive */
data[index] = arr[i];
combinationUtil(arr, data, i + , end, index + , r); /* call selfe */
}
} long factorial(int m, int n) { /* caculate combination number */
int i, j;
long ans = ;
if (m < n - m) m = n - m; /* C(m,n)=C(n-m,n) */
for (i = m + ; i <= n; i++) ans *= i;
for (j = ; j <= n - m; j++) ans /= j;
return ans; /* answer */
} int main() {
long combNum;
int t, s, i, j, k, nn, nc, ans, numOf1, * candiNum, * combSet, * tmptr; /* init vars */
nc = ;
scanf("%d %d %d", &t, &n, &s); /* get &t, &n, &s */
if (t < || n <= || n <= s || s < ) {printf("-1\n"); return ;} /* error input */
if (n == ) {printf("0\n"); return ;}
nn = n - ; /* first layer */
map = malloc(sizeof(int *)*n); /* alloc for 2dim array: map */
tmptr = malloc(sizeof(int) * n * n);
for (i = ; i < n; ++i) {
map[i] = tmptr; /* set 2dim to 1dim */
tmptr += n;
}
dp = malloc(sizeof(int *) * ( << n)); /* alloc for 2dim array: map */
tmptr = malloc(sizeof(int) * ( << n) * n);
for (i = ; i < ( << n); ++i) {
dp[i] = tmptr; /* set 2dim to 1dim */
tmptr += n;
}
for (i = ; i < n; i++) /* get the map */
for (j = ; j < n; j++)
scanf("%d", &map[i][j]); /* store */ memset(dp[], -, sizeof(int) * ( << n) * n); /* fill all dp with -1 */
for ( i = ; i < n; ++i )
dp[ << i ][ i ] = map[ ][ i ]; while (t > nn) { /* let all thread have work to do */
nn *= (nn - );
nc += ; /* extends layers until t > nn */
}
j = ;
candiNum = malloc(sizeof(int) * (n - ));
for (i = ; i < n; ++i) { /* candiNum set means all cities exclude the start piont*/
if (i != s) {
candiNum[j] = i; /* exclude the start point */
j++;
}
}
combNum = factorial(nc, n - ); /* calculate combination number */
combSet = malloc(sizeof(int) * combNum);
combptr = combSet;
numOf1 = n - - nc; /* there should have `numOf1` of 1 in every item */
setCombination(candiNum, n - , numOf1);
posOf1 = malloc(sizeof(int) * numOf1); /* arr to store the position of 1 in each comb */
k = ;
for (i = ; i < combNum; ++i) {
tmptr = posOf1;
for (j = ; j < n; ++j) { /* go through all bits */
if ((combSet[i] & ( << j)) != ) {
*tmptr = j;
tmptr++; /* point to next cell */
}
}
for (j = ; j < numOf1; ++j) { /* arrange jobs */
args[k].id = k; /* set thread id arg */
args[k].startPoint = posOf1[j];
args[k].setMask = combSet[i]; /* set thread set mask args */
pthread_join(tid[k], NULL);
pthread_create(&tid[k], NULL, trd, &args[k]); /* new thread */
k = (k + ) % t;
}
}
for (i = ; i < t; ++i)/* join all thread */
pthread_join(tid[i], NULL);
ans = TSP(, ( << n ) - );
if (ans == 1e9)
printf("-1\n");
else
printf("%d\n", ans); /* final answer */
free(dp[]); /* free */
free(dp);
free(map[]); /* free */
free(map);
free(posOf1); /* free */
free(combSet);
free(candiNum); /* free */
return ;
}
// file tsp.h
typedef struct threadArgs {
int id;
int startPoint;
int setMask;
} trdArgs;
int TSP( int x, int setMask );
void * trd(void * SArg) ;
void combinationUtil(int arr[], int data[], int start, int end,
int index, int r);
void setCombination(int arr[], int n, int r) ;
long factorial(int m, int n) ;
Makefile:
CC=gcc
CFLAGS=-Wpedantic -Wall -Wextra -Werror -std=c89 -pthread -D_GNU_SOURCE tsp.o: tsp.c tsp.h
${CC} ${CFLAGS} tsp_blog.c -o tsp.o
欢迎没有完全看懂的读者随时提问,文中的例程也不一定是最好的算法,还请有想法的读者能不吝赐教。
多线程动态规划算法求解TSP(Traveling Salesman Problem) 并附C语言实现例程的更多相关文章
- 【智能算法】用模拟退火(SA, Simulated Annealing)算法解决旅行商问题 (TSP, Traveling Salesman Problem)
喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号[程序猿声] 文章声明 此文章部分资料和代码整合自网上,来源太多已经无法查明出处,如侵犯您的权利,请联系我删除. 01 什么是旅行商问题(TS ...
- 旅行商问题(Traveling Salesman Problem,TSP)的+Leapms线性规划模型及c++调用
知识点 旅行商问题的线性规划模型旅行商问题的+Leapms模型及CPLEX求解C++调用+Leapms 旅行商问题 旅行商问题是一个重要的NP-难问题.一个旅行商人目前在城市1,他必须对其余n-1个城 ...
- 基于贪心算法求解TSP问题(JAVA)
概述 前段时间在搞贪心算法,为了举例,故拿TSP来开刀,写了段求解算法代码以便有需之人,注意代码考虑可读性从最容易理解角度写,没有优化,有需要可以自行优化! 详细 代码下载:http://www.de ...
- 基于爬山算法求解TSP问题(JAVA)
一.TSP问题 TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题.货郎担问题,是数学领域中著名问题之一.假设有一个旅行商人要拜访n个城市,他必须选 ...
- MIP经典问题:旅行商问题 (traveling salesman problem)
*本文主要记录和分享学习到的知识,算不上原创. *参考文献见链接. 旅行商问题.背包问题都是0-1规划问题中最为经典的问题. 通常来说,当我们学习并熟悉一种求解混合整数问题的技巧时,可以用这种技巧来求 ...
- Complexity and Tractability (3.44) - The Traveling Salesman Problem
Copied From:http://csfieldguide.org.nz/en/curriculum-guides/ncea/level-3/complexity-tractability-TSP ...
- 利用HTML5 Canvas和Javascript实现的蚁群算法求解TSP问题演示
HTML5提供了Canvas对象,为画图应用提供了便利. Javascript可执行于浏览器中, 而不须要安装特定的编译器: 基于HTML5和Javascript语言, 可随时编写应用, 为算法測试带 ...
- TSP(Traveling Salesman Problem)-----浅谈旅行商问题(动态规划,回溯实现)
1.什么是TSP问题 一个售货员必须访问n个城市,这n个城市是一个完全图,售货员需要恰好访问所有城市的一次,并且回到最终的城市. 城市于城市之间有一个旅行费用,售货员希望旅行费用之和最少. 完全图:完 ...
- 蚁群算法求解TSP问题
一.蚁群算法简介 蚁群算法是对自然界蚂蚁的寻径方式进行模似而得出的一种仿生算法:蚂蚁在运动过程中,能够在它所经过的路径上留下信息素(pheromone)的物质进行信息传递,而且蚂蚁在运动过程中能够感知 ...
随机推荐
- LeetCode(35)-Path Sum
题目: Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up ...
- Android性能优化之UI渲染性能优化
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 本篇博客主要记录一些工作中常用的UI渲染性能优化及调试方法,理解这些方法对于我们编写高质量代码也是有一些帮助的,主要内容包括介绍CPU,GPU的职 ...
- MQ队列与哪些机器连接
1.使用MQ安装用户登录Linux,例如:su - mqm 2.runmqsc Qm1 #Queue 代表要查询的队列3.DISPLAY CONN(*) WHERE(OBJNAME EQ Queue) ...
- 知物由学|游戏开发者如何从容应对Unity手游风险?
本文由 网易云发布. "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不 ...
- package.json字段全解
原文:http://blog.csdn.net/woxueliuyun/article/details/39294375 Name 必须字段. 小提示: 不要在name中包含js, node字样: 这 ...
- Solr 新增、更新、删除索引
solr-admin新增索引 [索引中无则新增,有则更新] 1.在doc标签和field标签中增加权重(boost),增加权重后,可以在搜索的时候做权重过滤. <add> <doc ...
- 关于eclipse运行TestNG出现: CreateProcess error=206, ÎļþÃû»ò)չÃû的解决办法
最近玩物流宝的一个项目,需要测试下3个系统打通的接口. 不测不要紧,一测吓一跳.我的乖乖:几百个bean被加进来.就凭我这肉机,内存不爆才怪. 于是换一套方案,用了另一个测试接口. 但是这个测试接口, ...
- golang升级
系统安装软件一般在/usr/share,可执行的文件在/usr/bin,配置文件可能安装到了/etc下等. 文档一般在 /usr/share 可执行文件 /usr/bin 配置文件 /etc lib文 ...
- Python-网站页面代码获取
Python3.6 库:urllib3, bs4 主程序是抓取亚马逊图书销售排名数据,但是亚马逊应该是加了反爬虫,拒绝疑似机器人的请求,这部分暂时以百度代替. 其实简单的页面抓取,常用的urllib. ...
- java数据库之JDBC
任何一个项目,都离不开数据,而对于数据的存储以及其他操作,就会用到数据库了. 在这里是主要针对MySQL数据库的操作. 1.软件 当然首先要下载MySQL,为了操作起来更加方便,这里推荐一个比较方便的 ...