动态规划:旅行售货员问题
- 格式:doc
- 大小:249.50 KB
- 文档页数:14
【算法复习二】货郎担(旅行售货商)动态规划一,问题由来货郎担问题也叫旅行商问题,即TSP问题(Traveling Salesman Problem),是数学领域中著名问题之一。
二,问题描述1)货郎担问题提法:有n个城市,用1,2,…,n表示,城i,j之间的距离为dij,有一个货郎从城1出发到其他城市一次且仅一次,最后回到城市1,怎样选择行走路线使总路程最短?2)旅行商问题的提法:假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路经的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。
路径的选择目标是要求得的路径路程为所有路径之中的最小值。
三,问题求解1)动态规划解例题:设v1,v2,……..,vn是已知的n个城镇,城镇vi到城镇vj的距离为dij,现求从v1出发,经各城镇一次且仅一次返回v1的最短路程。
分析:设S表示从v1到vi中间所可能经过的城市集合,S实际上是包含除v1和vi两个点之外的其余点的集合,但S中的点的个数要随阶段数改变。
建模:状态变量(i,S)表示:从v1点出发,经过S集合中所有点一次最后到达vi。
最优指标函数fk(i,S)为从v1出发,经过S集合中所有点一次最后到达vi。
决策变量Pk(i,S)表示:从v1经k个中间城镇的S集合到vi 城镇的最短路线上邻接vi的前一个城镇,则动态规划的顺序递推关系为:fk(i,S)= min{ fk-1(j,S、{ j }+dji} j属于Sf0(i,空集)=d1i (k=1,2,…,n-1,i=2,3,…n)求解:K=0f0(2,空集)=d12=6f0(3,空集)=d13=7f0(4,空集)=d14=9当k=1时:从城市V1出发,经过1个城镇到达Vi的最短距离为:f1(2,{ 3 }) = f0 (3,空)+d 32 =7+8=15f1(2,{ 4 }) = f0 (4,空)+d 42 =9+8=14f1(3,{ 2 }) = f0 (2,空)+d 23 =6+9=15f1(3,{ 4 }) = f0 (4,空)+d 43 =9+5=14f1(4,{ 2 }) = f0 (2,空)+d 24 =6+7=13f1(4,{ 3 }) = f0 (3,空)+d 34 =7+8=15当k=2时,从城市V1出发,中间经过2个城镇到达Vi的最短距离.f2(2,{ 3,4 }) = min[ f1(3,{4})+d32, f1(4,{3})+ d42] =min[14+8,15+5]=20P2(2,{3,4})=4f2(3,{ 2,4 })= min[14+9,13+5]=18P2(3,{2,4})=4f2(4,{ 2,3})= min[15+7,15+8]=22P2(4,{2,3})=2当k=3时:从城市V1出发,中间经过3个城镇最终回到Vi的最短距离.f3(1,{ 2,3,4 })= min[f2(2,{ 3,4 }) + d 21,f2(3,{ 2,4})+ d31,f2(4,{ 2,3 }) + d41]=min[20+8,18+5,22+6]=23P3(1,{2,3,4})=3逆推回去,货郎的最短路线是1 2 4 3 1,最短距离为23.四,源码[html] view plaincopyprint?1.#include<iostream>2.#include<iomanip>ing namespace std;4.5.int n;6.int cost[20][20]={};7.bool done[20]={1};8.int start = 0; //从城市0开始9.10.int imin(int num, int cur)11.{12.if(num==1) //递归调用的出口13.return cost[cur][start]; //所有节点的最后一个节点,最后返回最后一个节点到起点的路径14.15.int mincost = 10000;16.for(int i=0; i<n; i++)17.{18.cout<<i<<" i:"<<done[i]<<endl;19.if(!done[i] && i!=start) //该结点没加入且非起始点20.{21.if(mincost <= cost[cur][i]+cost[i][start])22.{23.continue; //其作用为结束本次循环。
组合优化中的旅行推销员问题旅行推销员问题(Traveling Salesman Problem,TSP)是组合优化领域中最经典的问题之一。
该问题的基本描述是:给定一组城市和城市之间的距离,寻找一条最短的路径,使得经过每个城市一次且最终回到起始城市。
TSP在实际生活中有着广泛的应用,例如物流配送、电路板设计和基因序列分析等。
1. 问题描述在旅行推销员问题中,我们考虑有n个城市,城市之间的距离由一个n×n的矩阵D表示。
矩阵D中的元素D[i][j]表示城市i到城市j的距离。
该问题的目标是找到一条从某个城市出发,经过每个城市一次且最终回到出发城市的最短路径。
2. 算法解决方案在组合优化中,各种算法被提出来解决旅行推销员问题。
以下介绍两种常见的解决方案。
2.1 蛮力搜索算法蛮力搜索算法是一种穷举所有可能路径的方法。
其基本思想是通过尝试所有可能的路径组合,计算每条路径的总长度,最后找到最短路径。
蛮力搜索算法的缺点是时间复杂度较高,随着城市数量增加,搜索空间呈指数级增长。
2.2 近似算法近似算法是为了在较短的时间内找到接近于最优解的解决方案。
其中最著名的算法是最近邻算法。
该算法从一个起始城市出发,每次选择距离当前城市最近的未访问城市作为下一个访问城市,直到所有城市都被访问过,并返回起始城市。
虽然最近邻算法不能保证找到最优解,但其时间复杂度低,适用于大规模问题。
3. 实例分析为了更好地理解旅行推销员问题的解决方法,以一个具体的实例进行分析。
假设有5个城市,城市之间的距离矩阵D如下:城市A 城市B 城市C 城市D 城市E城市A 0 1 2 3 4城市B 1 0 5 6 7城市C 2 5 0 8 9城市D 3 6 8 0 10城市E 4 7 9 10 0使用蛮力搜索算法,我们需要计算出所有可能的路径,并计算每条路径的长度,最终找到最短路径。
在本例中,共有5个城市,所以共有5个阶乘(5!= 120)条可能的路径。
基于动态规划算法的旅行商问题求解旅行商问题(Traveling Salesman Problem,TSP)是一个经典的组合优化问题。
它的任务是在给定一系列城市和每对城市之间的距离(或者称为成本),找到一条经过每个城市一次且回到起始城市的最短路径。
动态规划算法是求解旅行商问题的一种常用方法。
它基于以下思想:将大问题分解为若干个小问题,通过解决小问题的最优解来逐步得到大问题的最优解。
首先,我们需要定义旅行商问题的状态。
在本问题中,我们可以将状态定义为一个二元组 (i, S),它表示旅行商当前所在的城市为 i,已经遍历过的城市集合为 S。
这里的状态空间是城市集合 C 的幂集除去空集:状态空间:C = {1, 2, ..., n},其中 n 是城市的数量。
接下来,我们需要定义状态转移方程。
假设当前状态为 (i, S),我们需要求解的是到达状态 (i, C) 的最短路径。
状态转移方程可以表示为:dp[i][S] = min{dist[i][j] + dp[j][S\{j}]},其中 j∈S,j≠i其中,dp[i][S] 是从城市 i 出发,经过集合 S 中的城市,最后回到起始城市的最短路径长度。
dist[i][j] 表示城市 i 到城市 j 的距离。
S\{j} 表示从集合 S 中去掉元素 j 后的集合。
最后,我们需要定义问题的边界条件。
当集合 S 中只有一个城市 i 时,经过城市 i 后回到起始城市的路径长度就是从起始城市到达城市 i 的距离。
所以边界条件可以表示为:当 |S| = 1 时,dp[i][S] = dist[i][1]接下来,我们使用动态规划算法来解决旅行商问题。
1. 创建一个二维数组 dp[n][2^n],其中 n 是城市的数量。
初始化数组 dp 的所有元素为无穷大。
2. 对于每个城市 i,将 dp[i][∅](空集)的值设为 dist[i][1]。
3. 对于集合 S 的大小从 2 到 n-1 的每个值,依次遍历每个城市 i。
旅⾏售货员问题(分⽀限界法)⼀、实验内容运⽤分⽀限界法解决0-1背包问题(或者旅⾏售货员问题、或者装载问题、或者批处理作业调度)使⽤优先队列式分⽀限界法来求解旅⾏售货员问题⼆、所⽤算法基本思想及复杂度分析1.算法基本思想分⽀限界法常以⼴度优先或以最⼩耗费有限的⽅式搜索问题的解空间树。
问题的解空间树是表⽰问题解空间的⼀棵有序树,常见的有⼦集树和排列树。
在搜索问题的解空间树时,分⽀限界法和回溯法的主要区别在于它们对当前扩展节点所采⽤的扩展⽅式不同。
在分⽀限界法中,每⼀个活结点只有⼀次机会成为扩展节点。
活结点⼀旦成为扩展节点,就⼀次性产⽣其所有⼉⼦节点。
在这些⼉⼦节点中,导致不可⾏解或导致⾮最优解的⼉⼦节点被舍弃,其余⼉⼦节点被加⼊活结点表中。
此后,从活结点表中取下⼀节点为当前扩展节点。
并重复上述节点扩展过程。
这个过程移⾄持续到找到所需的解或活结点表为空为⽌。
从活结点表中选择下⼀扩展节点的不同⽅式导致不同的分⽀限界法。
最常见的有以下两种⽅式:(1)队列式分⽀限界法队列式分⽀限界法将活结点表组织成⼀个队列,并按队列的先进先出原则选取下⼀个节点为当前扩展节点。
(2)优先队列式分⽀限界法优先队列式的分⽀限界法将活结点表组织成⼀个优先队列,并按优先队列中规定的节点优先级选取优先级最⾼的下⼀个节点成为当前扩展节点。
2.问题分析及算法设计问题分析:(1)解旅⾏售货员问题的优先队列式分⽀限界法⽤优先队列存储活结点表。
(2)活结点m在优先队列中的优先级定义为:活结点m对应的⼦树费⽤下界lcost。
(3)lcost=cc+rcost,其中,cc为当前结点费⽤,rcost为当前顶点最⼩出边费⽤加上剩余所有顶点的最⼩出边费⽤和。
(4)优先队列中优先级最⼤的活结点成为下⼀个扩展结点。
(5)排列树中叶结点所相应的载重量与其优先级(下界值)相同,即:叶结点所相应的回路的费⽤(bestc)等于⼦树费⽤下界lcost的值。
算法设计:(1)要找最⼩费⽤旅⾏售货员回路,选⽤最⼩堆表⽰活结点优先队列。
旅行售货员问题传统的旅行售货员问题可以概述如下:设有n个城市,以V1 ,V2 ,…V n表示之,用d ij表示城市V i到城市V j之间的距离,一个推销员从某城市(不妨假设从城市V1)出发到其他城市去一次而且仅一次,然后回到原来的城市(城市V1),问其应该选择什么样的行走路线才能使得总的旅行路程最短。
把这个问题抽象成图论问题就是给定一个连通无向图G(V,E),其中V={V1 ,V2,…V n}是顶点(城市)的集合,E是顶点间边的集合,表示相应城市间的旅行路线,即E={e=(V i,V j);顶点V i与V j间有边e},每个边e∈E上有非负权重w(e)=w(V i,V j)=d ij,表示顶点V i即城市V i到顶点V i(即城市V j)的距离,问题就是要确定图G的一个圈,它过每个顶点一次且仅一次,而且使得该圈的总权(即圈上所有边的权之和)最小。
这个圈在图论里称为Hamilton圈,简称H圈。
由于G(V,E)的边是无向的,相邻的边和顶点可以通过边进入顶点,也可以通过顶点进入边。
这样,寻找售货员最优旅行路线的问题就可以归结为如下的图论问题:给定连通图G(V,E),对每条边e= (V i,V j)∈E对应的顶点V i与V j间添加反向边e'=(V j,V i),得到双倍于G的另外一个连通图G'=(V,E'),求E1⊆E'使得G1(V,E1)为H圈且总权为∑w(e)最∈E1e小。
为叙述方便,若e= (V i ,V j ),则记为e i ,j ∈E,而相应的添加边为e j ,i 。
与边e i ,j ∈E '相对应,设定一个0-1整数变量x i ,j 。
若e i ,j ∈E ',即称边是从V i 到V j 的,或称为弧。
这样,我们就可以把无向图理解为有向图D (V,E)。
每一个E 1⊆E '唯一对应一组x i ,j 的值,反之亦然。
我们称x i ,j 为E 1的值系。
旅行售货员问题描述:由n个顶点构成的图,要求走遍所有的点且每个顶点只能走一次,最后回到出发点,使所走过的路径和最小,求最小值。
算法描述:通常以广度优先或以最小耗费优先的方式搜索问题的解空间树,只需求出一个解或是在满足约束条件的解中找出在某种意义下的最优解即结束遍历,在分支限界法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。
在这些儿子结点中,不可行解或非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。
这个过程一直持续到找到所需的解或活结点表为空时为止。
算法设计:1.构造出解空间树(仅使用一个优先队列来存储活节点,其中每个活节点都储存从根到该活结点的相应路径)2.利用二维数组保存图信息City_Graph[MAX_SIZE][MAX_SIZE],其中City_Graph[i][j]的值代表的是城市i与城市j之间的路径费用,一旦一个城市没有通向另外城市的路,则不可能有回路,不用再找下去了3. 我们任意选择一个城市,作为出发点。
(因为最后都是一个回路,无所谓从哪出发)举例:某售货员要到若干个城市推销商品,已知各城市之间的路程(或旅费)。
他要选定一条从驻地出发,经过每个城市一遍最后回到驻地的路线,使总的路线(或总的旅费最小)。
程序:#include "stdafx.h"#include <stdio.h>#include <istream>using namespace std;//---------------------宏定义------------------------------------------#define MAX_CITY_NUMBER 10 //城市最大数目#define MAX_COST 10000000 //两个城市之间费用的最大值//---------------------全局变量----------------------------------------int City_Graph[MAX_CITY_NUMBER][MAX_CITY_NUMBER];//表示城市间边权重的数组int City_Size; //表示实际输入的城市数目int Best_Cost; //最小费用int Best_Cost_Path[MAX_CITY_NUMBER];//最小费用时的路径//------------------------定义结点--------------------------------------- typedef struct Node{int lcost; //优先级int cc; //当前费用int rcost; //剩余所有结点的最小出边费用的和int s; //当前结点的深度,也就是它在解数组中的索引位置int x[MAX_CITY_NUMBER]; //当前结点对应的路径struct Node* pNext; //指向下一个结点}Node;//---------------------定义堆和相关对操作-------------------------------- typedef struct MiniHeap{Node* pHead; //堆的头}MiniHeap;//初始化void InitMiniHeap(MiniHeap* pMiniHeap){pMiniHeap->pHead = new Node;pMiniHeap->pHead->pNext = NULL;}//入堆void put(MiniHeap* pMiniHeap, Node node){Node* next;Node* pre;Node* pinnode = new Node; //将传进来的结点信息copy一份保存//这样在函数外部对node的修改就不会影响到堆了pinnode->cc = ;pinnode->lcost = node.lcost;pinnode->pNext = node.pNext;pinnode->rcost = node.rcost;pinnode->s = node.s;pinnode->pNext = NULL;for (int k = 0; k<City_Size; k++){pinnode->x[k] = node.x[k];}pre = pMiniHeap->pHead;next = pMiniHeap->pHead->pNext;if (next == NULL){pMiniHeap->pHead->pNext = pinnode;}else{while (next != NULL){if ((next->lcost) >(pinnode->lcost)){ //发现一个优先级大的,则置于其前面pinnode->pNext = pre->pNext;pre->pNext = pinnode;break; //跳出}pre = next;next = next->pNext;}pre->pNext = pinnode; //放在末尾}}//出堆Node* RemoveMiniHeap(MiniHeap* pMiniHeap){Node* pnode = NULL;if (pMiniHeap->pHead->pNext != NULL){pnode = pMiniHeap->pHead->pNext;pMiniHeap->pHead->pNext = pMiniHeap->pHead->pNext->pNext;}return pnode;}//---------------------分支限界法找最优解--------------------------------void Traveler(){int i, j;int temp_x[MAX_CITY_NUMBER];Node* pNode = NULL;int miniSum; //所有结点最小出边的费用和int miniOut[MAX_CITY_NUMBER];//保存每个结点的最小出边的索引MiniHeap* heap = new MiniHeap; //分配堆InitMiniHeap(heap); //初始化堆miniSum = 0;for (i = 0; i<City_Size; i++){miniOut[i] = MAX_COST; //初始化时每一个结点都不可达for (j = 0; j<City_Size; j++){if (City_Graph[i][j]>0 && City_Graph[i][j]<miniOut[i]){//从i到j可达,且更小miniOut[i] = City_Graph[i][j];}}if (miniOut[i] == MAX_COST){// i 城市没有出边Best_Cost = -1;return;}miniSum += miniOut[i];}for (i = 0; i<City_Size; i++){ //初始化的最优路径就是把所有结点依次走一遍Best_Cost_Path[i] = i;}Best_Cost = MAX_COST; //初始化的最优费用是一个很大的数pNode = new Node; //初始化第一个结点并入堆pNode->lcost = 0; //当前结点的优先权为0 也就是最优pNode->cc = 0; //当前费用为0(还没有开始旅行)pNode->rcost = miniSum; //剩余所有结点的最小出边费用和就是初始化的miniSumpNode->s = 0; //层次为0pNode->pNext = NULL;for (int k = 0; k<City_Size; k++){pNode->x[k] = Best_Cost_Path[k]; //第一个结点所保存的路径也就是初始化的路径}put(heap, *pNode); //入堆while (pNode != NULL && (pNode->s) < City_Size - 1){//堆不空不是叶子for (int k = 0; k<City_Size; k++){Best_Cost_Path[k] = pNode->x[k]; //将最优路径置换为当前结点本身所保存的}/** * pNode 结点保存的路径中的含有这条路径上所有结点的索引* * x路径中保存的这一层结点的编号就是x[City_Size-2]* * 下一层结点的编号就是x[City_Size-1]*/if ((pNode->s) == City_Size - 2){ //是叶子的父亲int edge1 = City_Graph[(pNode->x)[City_Size - 2]][(pNode->x)[City_Size - 1]];int edge2 = City_Graph[(pNode->x)[City_Size - 1]][(pNode->x)[0]];if (edge1 >= 0 && edge2 >= 0 && (pNode->cc + edge1 + edge2) < Best_Cost){ //edge1 -1 表示不可达//叶子可达起点费用更低Best_Cost = pNode->cc + edge1 + edge2;pNode->cc = Best_Cost;pNode->lcost = Best_Cost; //优先权为 Best_CostpNode->s++; //到达叶子层}}else{//内部结点for (i = pNode->s; i<City_Size; i++){ //从当前层到叶子层if (City_Graph[pNode->x[pNode->s]][pNode->x[i]] >= 0){ //可达//pNode的层数就是它在最优路径中的位置int temp_cc = pNode->cc + City_Graph[pNode->x[pNode->s]][pNode->x[i]];int temp_rcost = pNode->rcost - miniOut[pNode->x[pNode->s]];//下一个结点的剩余最小出边费用和//等于当前结点的rcost减去当前这个结点的最小出边费用if (temp_cc + temp_rcost<Best_Cost){ //下一个结点的最小出边费用和小于当前的最优解,说明可能存在更优解for (j = 0; j<City_Size; j++){ //完全copy路径,以便下面修改temp_x[j] = Best_Cost_Path[j];}temp_x[pNode->x[pNode->s + 1]] = Best_Cost_Path[i];//将当前结点的编号放入路径的深度为s+1的地方temp_x[i] = Best_Cost_Path[pNode->s + 1]; //??????????????//将原路//径中的深度为s+1的结点编号放入当前路径的//相当于将原路径中的的深度为i的结点与深度W为s+1的结点交换Node* pNextNode = new Node;pNextNode->cc = temp_cc;pNextNode->lcost = temp_cc + temp_rcost;pNextNode->rcost = temp_rcost;pNextNode->s = pNode->s + 1;pNextNode->pNext = NULL;for (int k = 0; k<City_Size; k++){pNextNode->x[k] = temp_x[k];}put(heap, *pNextNode);delete pNextNode;}}}}pNode = RemoveMiniHeap(heap);}}int main(){int i, j;scanf_s("%d", &City_Size);for (i = 0; i<City_Size; i++){for (j = 0; j<City_Size; j++){scanf_s("%d", &City_Graph[i][j]);}}Traveler();printf("%d /n", Best_Cost);system("pause");return 1;}。
动态规划算法的应用课程名称:****************院系:**************************学生姓名:******学号:************专业班级:***************************** 指导教师:******2013年12月27日动态规划的应用摘要:旅行商问题(TSP问题)时是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。
该问题又称为货郎担问题、邮递员问题、售货员问题,是图问题中最广为人知的问题。
动态规划( dynamic programming )算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强。
利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。
本次课程设计运用动态规划解决旅行售货员问题,动态规划的基本思想是:把求解的问题分成许多若干阶段或许多子问题,然后按顺序求解各子问题。
前一子问题的解,为后一子问题的求解提供了有用的信息,在求解任一子问题时列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。
依次解决各子问题,最后一个子问题就是初始问题的解。
通过图的关系矩阵来表示个城市之间的关系,二维数组表示顶点之间的距离关系,对子问题进行求解比较,最后得出所求结果。
关键字:旅行商问题动态规划法图矩阵目录第一章绪论 (1)1.算法介绍 (1)2.算法应用 (1)第二章动态规划理论知识 (2)2.1动态规划的基本思想 (2)2.2动态规划设计步骤 (2)第三章旅行售货员问题 (3)3.1问题描述:旅行售货员问题 (3)3.2算法设计内容 (3)3.3算法分析 (3)3.4流程图 (4)第四章物流配送网络 (5)第五章结论 (7)参考文献 (8)附件 (9)第一章绪论1.算法介绍动态规划( dynamic programming )是解决多阶段决策过程最优化问题的一种数学方法。
旅⾏售货员问题旅⾏售货员问题【题⽬】某售货员要到4个城市去推销商品,已知各城市之间的路程,如右图所⽰。
请问他应该如何选定⼀条从城市1出发,经过每个城市⼀遍,最后回到城市1的路线,使得总的周游路程最⼩?并分析所设计算法的计算时间复杂度。
【分析】该题利⽤回溯法求解,此时需要确定解空间的类型:我们可以知道该解空间为⼀棵排列树。
我们假设初始的⼀条路线为x,x中的值为 1,2,3,……,n,表⽰该路线为由城市1依次经过城市2、3……到n后再回到城市1(当然是假设该路线存在)。
如果不存在的话,我们只需改变⼀下这个排列的排列⽅式,再进⾏判断,所以可想⽽知,我们可以知道该解空间是⼀棵排列树。
当然上述的说法,只是单纯的穷举,这并不是我们想要的回溯法,我们通过递归实现,在递归的过程中适当地“剪枝”即除去那些不可能形成最优解的解。
现在我们来确定⼀下可⾏的约束条件,当我们进⾏递归搜索,搜索到第t层时,我们需要判断⼀下x[t]所代表的城市是否与上⼀层x[t-1]所代表的城市有“路”,如果没有的话,需要改变x[t]的值,然后继续上述判断,当出现⼀个满⾜条件的x[t]后还要判断当前从1到t-1所⾛的路程cc加上x[t]与x[t-1]的距离是否⼩于当前已经记录的最优解(最优解的初始值是⼀个⾜够⼤的数),如果到t的距离⽐当前最优解还要⼤的话,那么再以这条路线搜索下去的话回到城市1的路程⼀定⽐当前最优解还⼤,所以我们没有必要对这条路线进⾏下⼀步的搜索。
最后我们来确定当搜索到叶⼦结点的时候我们该如何处理?已知搜索到t层时,若t = n,说明已经搜索到了叶⼦结点,这个时候我们还需做上述所说的两个判断,如果两个判断都通过的话,说明该解⽐当前最优解还优,那么我们需要将该解记录下来,并记录该解的最优值。
【伪代码】void travel(int t) {if(t到达第n层即搜索到叶⼦结点) {if(城市x[t-1]可以到达城市x[t],并且城市x[t]可以回到城市1,且此时所⾛的路程cc加上x[t-1]与x[t]的距离和x[t]与1的距离⼩于当前最优值bestc) {将最优解记录下来;将最优值记录下来;}return;}for(int i = t; i < n; i++) {if(城市x[t-1]能达到城市x[i]即这两个城市间有边,并当前所⾛的路程cc加上这两个城市的距离没有⽐当前最优值bestc⼤) {swap(x[i], x[t]);修改此时所⾛的路程cc;进⼊下⼀层递归;恢复原来cc的值;swap(x[i], x[t]);}}}【程序】⽤C++语⾔编写程序,代码如下:#include<iostream>using namespace std;const int INF = 10000000;int n, cc = 0, bestc = INF;int **g;int *x, *bestx;void travel(int t) {if (t == n) {if (g[x[t - 1]][x[t]] != INF && g[x[t]][1] != INF &&(cc + g[x[t - 1]][x[t]] + g[x[t]][1] < bestc || bestc == INF)) { for (int i = 0; i < n + 1; i++)bestx[i] = x[i];bestc = cc + g[x[t - 1]][x[t]] + g[x[t]][1];}return;}for (int i = t; i < n; i++) {if (g[x[t - 1]][x[i]] != INF && (cc + g[x[t - 1]][x[i]] < bestc|| bestc == INF)) {swap(x[i], x[t]);cc += g[x[t - 1]][x[t]];travel(t + 1);cc -= g[x[t - 1]][x[t]];swap(x[i], x[t]);}}}void output() {cout << bestc << endl;cout << bestx[1];for (int i = 2; i < n + 1; i++)cout << " " << bestx[i];cout << " " << bestx[1] << endl;}int main() {n = 4;g = new int*[n + 1];x = new int[n + 1];bestx = new int[n + 1];for (int i = 0; i < n + 1; i++) {g[i] = new int[n + 1];x[i] = i;for (int j = 0; j < n + 1; j++)g[i][j] = INF;}}g[1][2] = g[2][1] = 30;g[1][3] = g[3][1] = 6;g[1][4] = g[4][1] = 4;g[2][3] = g[3][2] = 5;g[2][4] = g[4][2] = 10;g[3][4] = g[4][3] = 20;travel(2);output();return 0;}【结果】先设置好城市间的距离,调⽤回溯⽅法,输出最优值(最⼩路程)和最优解(路线):该算法的时间复杂度为O(n!)。
TSP问题的动态规划解法第十七组:3103038028 郑少斌3103038029 王瑞锋3103038035 江飞鸿3103038043 韩鑫3103055004 唐万强1.TSP问题简介旅行商问题(Traveling Salesman Problem,简称TSP, 亦称为货单郎问题)可以描述为:对于N 个城市,它们之间的距离已知,有一旅行商要从某一城市走遍所有的城市,且每一城市只能经过一次,最后回到出发的城市,问如何选择路线可使他走过的路径最短。
这是一个典型的组合优化问题。
它有很强的现实意义,可以应用于交通运输,物资调配,旅游线路设置。
对于了解某个国家地理分布也有一定的现实意义。
这个问题的解法有很多种,在这里我们尝试使用最优控制中的动态规划的相关知识来进行求解。
2.TSP问题分析对于这个问题,我们首先想到的是应用穷举法进行解答,但是这个方法时间和空间的复杂度很高。
从表面上看,TSP 问题很简单,其实则不然。
对于N 个城市的TSP,存在的可能路径为(N-1)!/2条,当N较大时,其数量是惊人的。
计算每条路经都需求出N 个距离之和,这样各种路径及其距离之和的计算量正比与N!/2.用搜索法要求就规模大的TSP是不现实的。
例如使用1GFLOPs 次的计算机搜索TSP 所需的时间如下表所示 城市数7152050100200加法量 3105.2⨯ 11105.6⨯ 18102.1⨯ 64105.1⨯ 157105⨯ 37410搜索时间s 5105.2-⨯1.8h350yy 48105⨯ y 14210y 35810由上可知,对于这个问题采用穷举法进行解答是不现实的,这就要求我们采用其他的方法进行解答。
3. 其他求解TSP 问题的方法*贪心法a. 所谓贪心法,就是在组合算法中,将每一步都取局部最优的求解方法。
b. 下表表示用贪心法求解TSP 的过程。
先将各城市间的距离用行列式形式表示,主对角线上用∞表示。
xxxxxxxx大学结课论文项目动态规划算法解决旅行售货商问题课程名称: xxxxxxxxxxxxxx院系: xxxxxxxxxxxxxx学生姓名: xxxxxx学号: xxxxxxxxx指导教师: xxxxxx2015年6月15日摘要:旅行商问题(TSP问题)时是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。
该问题又称为货郎担问题、邮递员问题、售货员问题,是图问题中最广为人知的问题。
动态规划( dynamic programming )算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强。
利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。
本次课程设计运用动态规划解决旅行售货员问题,动态规划的基本思想是:把求解的问题分成许多若干阶段或许多子问题,然后按顺序求解各子问题。
前一子问题的解,为后一子问题的求解提供了有用的信息,在求解任一子问题时列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。
依次解决各子问题,最后一个子问题就是初始问题的解。
通过图的关系矩阵来表示个城市之间的关系,二维数组表示顶点之间的距离关系,对子问题进行求解比较,最后得出所求结果。
关键字:旅行商问题动态规划法图矩阵目录第一章绪论1.1算法介绍1.2算法应用第二章动态规划理论知识2.1动态规划的基本思想2.2动态规划设计步骤第三章旅行售货员问题3.1问题描述:旅行售货员问题3.2算法设计内容3.3算法分析3.4流程图第四章物流配送网络第五章结论第一章绪论1.1算法介绍动态规划( dynamic programming )是解决多阶段决策过程最优化问题的一种数学方法。
1951年美国数学家Bellman(贝尔曼)等人根据一类多阶段决策问题的特性,提出了解决这类问题的“最优性原理”,并研究了许多实际问题,从而创建了最优化问题的一种新方法——动态规划。
解决多阶段决策过程最优化问题,难度比较大,技巧性也很强。
利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。
动态规划算法将问题的解决方案视为一系列决策的结果,与贪婪算法不同的是,在贪婪算法中,每采用一次贪婪准则,便做出一个不可撤回的决策;而在动态规划算法中,还要考察每个最优决策序列中是否包含一个最优决策子序列,即问题是否具有最优子结构性质。
1.2算法应用动态规划在工程技术、管理、经济、工业生产、军事及现代控制工程等方面都有广泛的应用,而且由于动态规划方法有其独特之处,在解决某些实际问题时,显得更加方便有效。
由于决策过程的时间参数有离散的和连续的情况,故决策过程分为离散决策过程和连续决策过程。
这种技术采用自底向上的方式递推求值,将待求解的问题分解成若干个子问题,先求解子问题,并把子问题的解存储起来以便以后用来计算所需要求的解。
简言之,动态规划的基本思想就是把全局的问题化为局部的问题,为了全局最优必须局部最优。
第二章动态规划理论知识2.1动态规划的基本思想把求解的问题分成许多若干阶段或许多子问题,然后按顺序求解各子问题。
前一子问题的解,为后一子问题的求解提供了有用的信息,在求解任一子问题时列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。
依次解决各子问题,最后一个子问题就是初始问题的解。
简言之,动态规划的基本思想就是把全局的问题化为局部的问题,为了全局最优必须局部最优。
2.2动态规划设计步骤1)划分阶段:按照问题的时间或空间特征,把问题分为若干阶段。
这若干阶段一定要是有序的或可排序的(无后向性)。
2)选择状态:将问题发展到各个阶段时所出现的各种客观情况用不同的状态来表示出来。
状态的选择要有无后向性。
3)确定决策并写出状态转移方程:状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。
第三章旅行售货员问题3.1问题描述:旅行售货员问题某售货员要到若干城市去推销商品,已知各城市之间的路程。
他要选定一条从驻地出发,经过每一个城市一遍,最后回到驻地的路线,使总的路程最小,并求出最小路程。
3.2算法设计内容不同城市的路线和距离都不一样。
运用动态规划算法来设计本次课程设计,考虑到对问题进行阶段划分和状态的选择。
使用Left函数实现V'-{k} 的下标检索。
根据遍历城市的各个阶段时所出现的情况并用不同的状态表示出来。
当然这时的状态必须要满足无后向性。
设计第一阶段则是各顶点为空,然后给赋值。
依次遍历各城市,在TSP函数中得以实现。
假设4个顶点分别用0、1、2、3的数字编号,顶点之间的权值放在数组c[4][4]中。
首先按个数为1,2,3的顺序生成1,2,3个元素的子集存放在数组V[2n-1]中。
设数组d[n][2n-1]存放迭代结果,其中d[i][j]表示从顶点i经过子集V[j]中的顶点一次且仅一次,最后回到出发点0的最短路径长度。
3.3算法分析假设从顶点i出发,令d(i,V’)表示从顶点i出发经过V’中各个顶点一次且仅一次,最后回到出发点i的最短路径的长度,开始时,V’=V-{i},于是,旅行商问题的动态规划函数为:d(i,V’) = min{cik + d(k,V’-{k})} (k∈V’) 1)d(k,{}) = cki (k ≠ i) 2)简单来说,就是用递归表达:从出发点0到1号点,假设1是第一个,则剩下的路程就是从1经过剩下的点最后回到0点的最短路径. 所以当V’为空的时候, d(k,{}) = cki (k ≠ i),找的是最后一个点到0点的距离.递归求解1之后,再继续求V’之中剩下的点,最后找出min.如果按照这个思想直接做,对于每一个i都要递归剩下的V中所有的点,所以这样的时间复杂度就近似于N!,其中有很多重复的工作.可以从小的集合到大的集合算,并存入一个二维数组,这样当加入一个节点时,就可以用到之前的结果,如四个点的情况:邻接矩阵:node 0 1 2 30 5 3 21 5 7 92 3 7 123 2 9 12动态填表:表中元素代表第i个节点经过V集合中的点最后到0点的最短值.如果有多个值,取其中最小的一个.i\Vj 0 1 2 3 1,2(取min) 1,3(取min) 2,3(取min) 1,2,3(取min)0 c[0][i]+d[i][v’]=211 5 10 11 {c[1][2]+d[2][{3}]=21,c[1][3]+d[3][{2}]=24}2 3 12 14 {c[2][1]+d[1][{3}]=18,c[2][3]+d[3][{1}]=26}3 2 14 15 {c[3][1]+d[1][{2}]=19,c[3][2]+d[2][{1}]=24}这样一共循环(2^(N-1)-1)*(N-1)次,就把时间复杂度缩小到O(N*2N )的级别.核心伪代码如下:{for (i =1;i<n;i++)d[i][0]=c[i][0];for( j=1;j<2^(N-1)-1;j++)for(i=1 ; i<n ;i++){if(子集Vj中不包含i){对Vj中的每个元素k,计算d[i][Vj] = min{c[i][k] + d[k][{Vj-k}] | 每一个k∈Vj};}}对V[2^(n-1)-1]中的每个元素k,计算:d[0][2^(n-1)-1] = min{c[0][k] + d[k][2^(n-1)-2]};输出最短路径:d[0][2^(n-1)-1];}具体代码如下:// TravRoadD.cpp : Defines the entry point for the console application. //#include "stdafx.h"#include "windows.h"#include "math.h"#include <stdio.h>#include <ctime>#include <algorithm>using namespace std;int N;int matr[20][20];int d[20][40000]={0};int getmin(int *sum){int i = 0;int min = -1,k;for(;i<N;i++){if((min < 0 && sum[i]>0) || (sum[i]>0 && sum[i]<min)){min = sum[i];k = i;}}return min;}void getJ(int jlist[], int c, int n){int i = n-1,j;int tmp = 1 , result = 0;while(!jlist[i])i--;j = i-1;while(jlist[j])j--;if(!jlist[n-1]){jlist[i]=0;jlist[i+1]=1;}else if(n-1-j==c){for(i=0;i<n;i++)jlist[i]=0;for(i=0;i<c+1;i++)jlist[i]=1;}else{int k;k=n-1-j;while(!jlist[j])j--;for(i=0;j+i<n;i++)jlist[j+i]=0;for(i=0;i<=k;i++)jlist[j+i+1]=1;}}int getnextj( int j ){int nextj = 0;int c=0;int jlist[20]={0};int i=0;int tmp = 1;while(j){if(j%2){c++;jlist[i++]=1;}else{jlist[i++]=0;}j/=2;}getJ(jlist,c,N-1);for(i=0;i<N-1;i++){if(jlist[i])nextj += tmp;tmp *= 2;}return nextj;}int main(int argc, char* argv[]){freopen("d:\\test_20.txt","r",stdin);int i,j;int min;scanf("%d",&N);for(i = 0; i < N; i++){for(j = 0;j < N; j++){scanf("%d",&matr[i][j]);}}int V[20]={0};for(i = 0; i < N; i++)V[i]=1;V[0]=0;for (i =1;i<N;i++)d[i][0]=matr[i][0];for(j=1;j<pow(2,N-1)-1;j=getnextj(j))for(i=1; i<N ;i++){if(!(j & ( 1<<(i-1) ))){int jlist[20]={0};int tmpres[20]={0};int c=0,k=j,l;while(k){if(k%2){jlist[c++]=1;}else{jlist[c++]=0;}k/=2;}c=0;for(l=0;l<N;l++){if(jlist[l]){tmpres[c++]=matr[i][l+1] + d[l+1][j-(1<<l)];}}d[i][j] = getmin(tmpres);}}int tmpres[20]={0};j = pow(2,N-1)-1;for(i=1;i<N;i++){tmpres[i]=matr[0][i] + d[i][ j - (1<<(i-1) )];}min = getmin(tmpres);d[0][2^(n-1)-1] = min{matr[0][k] + d[k][2^(n-1)-2]};d[0][2^(n-1)-1];printf("%d\n",min);getchar();return 0;}3.4流程图YN3.5运行结果截图如下图4-1图4-1开始函数IsIncluded(int x,int array[3])判断x 是否包含在数组中。