实验三 旅行商问题

  • 一. 实验内容
  • 二.实验目的
  • 三. 算法描述
    • 1、回溯算法描述:
    • 2、分支限界法算法描述:
  • 四. 算法实现
    • 1.数据结构及函数说明
      • (1) 回溯法求解TSP问题
      • (2) 分支界限求解TSP问题:
    • 2.源程序代码
      • (1)回溯法求解TSP问题源代码:
      • (2)分支界限求解TSP问题
  • 五.程序运行结果
    • (1) 回溯法求解TSP问题运行结果:
    • (2) 分支界限求解TSP问题运行结果
  • 六.实验结果分析
  • 七.结论

一. 实验内容

运用分别编程实现回溯法和分支限界法求 TSP 问题的最优解,分析比较两种算法的时间复杂度并验证分析结果。

二.实验目的

1、掌握回溯法和分支限界法解决问题的一般步骤;
2、学会使用回溯法和分支限界法解决实际问题;
3、理解回溯法和分支限界法的异同及各自的适用范围。

三. 算法描述

1、回溯算法描述:

(1)文字描述

回溯算法()如果到达边界:记录当前的结果,进行处理如果没有到达边界:如果满足限界条件:(左子树)进行处理进行下一层的递归求解将处理回退到处理之前如果不满足限界条件:(右子树)进行下一层递归处理

(2)伪代码描述:
伪代码:

BACKTRACK-ITE()t = 1; //t为扩展结点在树中所处的层次while t > 0:if s(n,t) <= e(n,t) for i from s(n,t) to e(n,t) do x[t] = h(i)   //h[i]: 在当前扩展结点处x[t]的第i个可选值if CONSTRINT(t) && BOUND(t)  //满足约束限界条件if t > n      //已到叶子结点,输出结果OUTPUT(x);else t ++;//前进到更深层搜索elset --;  //回溯到上一层的活结点

2、分支限界法算法描述:

分支限界法就是先将根结点放入活结点表中,然后循环取出表头结点,如果满足约束条件和限界条件就可以将当前结点的子结点(或者说下一级结点)放入活结点表中,直至得到所求解或者是活结点表为空为止。根据活结点表的存储方式可以将分支限界法分为队列式分支限界法和优先队列式分支限界法。解空间是排列树,就是用一维数组遍历全排列,但是因为有剪枝,所以不会生成所有结点,也就不会得到全排列,只能得到其中的一部分。
采用优先队列式分支限界法,因为使用普通队列的话,在叶子节点的上一层(即倒数第二层)之前会生成所有的结点,这样达不到剪枝的目的。所以算法核心就是优先队列,约束条件和限界条件。
优先队列就是采用优先队列,或者说是最小堆来存储活结点表,结点当前路径长度短的优先级高。这里想说一句关于queue和priority_queue取队首元素的区别,queue是q.front(),而priority_queue是q.top()。
约束条件,一个是处理到某一路径的倒数第二个结点的时候,还有一个是在判断能否生成下一级结点并放入优先队列的时候。第一个情况是要求当前路径的倒数第二个顶点与倒数第一个顶点之间有边,还有倒数第一个顶点和起点之间有有边。第二种情况是当前路径的上一个已经确定的顶点和选择的顶点之间有边,如果没有就不能生成下一级结点。
限界条件就是当前结点的走过的路径长度<最短路径长度,这里的最短路径长度并不一定是最优解,而只是计算到某一步得到的最优解。如果当前结点不满足限界条件可以直接进行下一次循环,如果通过计算得到下一级结点不满足限界条件,那么就不能生成下一级结点。

四. 算法实现

1.数据结构及函数说明

(1) 回溯法求解TSP问题

数据结构如下:

int n;//定义图的顶点个数
int a[101][101];//定义图的邻接矩阵
int x[101];//定义当前解
int bestx[101];//定义当前最优解
int bestc;//定义当前当前值
int cc;//定义当前路径长度,形成环的时候与bestc比较看能不能更新bestc
函数如下:
void getmincost(int i)
{//如果访问到n个节点,要判断是否形成回路//如果当前值优于最优值,更新最优值和最优解if(i==n){//形成回路的条件就是x[n-1]与x[n]连通,x[n]与x[1]连通if(a[x[n-1]][x[n]]!=NoEdge&&a[x[n]][1]!=NoEdge){//说明形成了回路//如果当前值优于最优值,更新最优值和最优解//bestc=NoEdge说明还没有广搜到一条回路,那就先试着求出一个可行解if(cc+a[x[n-1]][x[n]]+a[x[n]][1]<bestc||bestc==NoEdge){for(int k=2;k<=n;k++)bestx[k]=x[k];bestc=cc+a[x[n-1]][x[n]]+a[x[n]][1];//更新最优值}}return ;}//当前在第i层,还得继续寻找else{for(int j=i;j<=n;j++){//判断是否可以进入x[j]子树//x[i-1]与x[j]连通使得1-i层连成一条路径且累计花费优于目前最优值//可以进入x[j]子树//这里要做的就是交换x[i]与x[j],进入i+1层//思想类似于n的全排列问题,递归求解//bestc=NoEdge说明还没有广搜到一条回路,那就先试着求出一个可行解//现在的解是x[1],x[2]...x[i]...x[j]...x[n]if(a[x[i-1]][x[j]]!=NoEdge&&cc+a[x[i-1]][x[j]]<bestc||bestc==NoEdge){//满足条件,可以交换//交换之后,现在的解是x[1],x[2]...x[j]...x[i]...x[n]swap(x[i],x[j]);//现在的解是x[1],x[2]...x[j]...x[i]...x[n]//此时第i个元素是==x[j]//第j个元素是==x[i]cc=cc+a[x[i-1]][x[i]];//更新路径的长度,进入i+1层getmincost(i+1);cc=cc-a[x[i-1]][x[i]];//还原路径的长度,比较x[j+1]子树swap(x[i],x[j]);//还原之前的解//现在的解是x[1],x[2]...x[i]...x[j]...x[n]}}}return ;}

(2) 分支界限求解TSP问题:

① 数据结构:

struct node
{int cl;//当前走过的路径长度int id;//处理的第几个景点int x[N];//记录当前路径node() {}node(int c,int i){cl=c;id=i;memset(x,0,sizeof(x));}
};
int m[N][N];//邻接矩阵存储无向带权图
int bestx[N];//最优解路径
int bestl;//最优解长度
int n,M;//景点数目,路径数目

② 主要函数说明

void bfs()
{priority_queue<node,vector<node>,cmp> q;node temp(0,2);//起点已经确定,从第2个景点开始int t;for(int i=1; i<=n; ++i)temp.x[i]=i;//初始化解向量q.push(temp);node live;//活结点while(!q.empty()){live=q.top();q.pop();t=live.id;if(t==n)//处理到倒数第二个景点{if(m[live.x[t-1]][live.x[t]]!=INF&&m[live.x[t]][1]!=INF)//满足约束条件,有路径{if(live.cl+m[live.x[t-1]][live.x[t]]+m[live.x[t]][1]<bestl)//更新最优解{bestl=live.cl+m[live.x[t-1]][live.x[t]]+m[live.x[t]][1];for(int i=1; i<=n; ++i)bestx[i]=live.x[i];}}continue;}if(live.cl>=bestl)//不满足限界条件continue;for(int j=t; j<=n; ++j) //排列树,j不能定义为整个函数的局部变量,循环过程中会出现混乱{if(m[live.x[t-1]][live.x[j]]!=INF&&live.cl+m[live.x[t-1]][live.x[j]]<bestl)//满足约束条件和限界条件{temp=node(live.cl+m[live.x[t-1]][live.x[j]],t+1);for(int k=1; k<=n; ++k)temp.x[k]=live.x[k];swap(temp.x[t],temp.x[j]);q.push(temp);}}}
}

2.源程序代码

(1)回溯法求解TSP问题源代码:

#include <iostream>
using namespace std;
#include<cstring>
#include<math.h>
#define NoEdge -1
int n;//定义图的顶点个数
int a[101][101];//定义图的邻接矩阵
int x[101];//定义当前解
int bestx[101];//定义当前最优解
int bestc;//定义当前当前值
int cc;//定义当前路径长度,形成环的时候与bestc比较看能不能更新bestc
void getmincost(int i)
{//如果访问到n个节点,要判断是否形成回路//如果当前值优于最优值,更新最优值和最优解if(i==n){//形成回路的条件就是x[n-1]与x[n]连通,x[n]与x[1]连通if(a[x[n-1]][x[n]]!=NoEdge&&a[x[n]][1]!=NoEdge){//说明形成了回路//如果当前值优于最优值,更新最优值和最优解//bestc=NoEdge说明还没有广搜到一条回路,那就先试着求出一个可行解if(cc+a[x[n-1]][x[n]]+a[x[n]][1]<bestc||bestc==NoEdge){for(int k=2;k<=n;k++)bestx[k]=x[k];bestc=cc+a[x[n-1]][x[n]]+a[x[n]][1];//更新最优值}}return ;}//当前在第i层,还得继续寻找else{for(int j=i;j<=n;j++){//判断是否可以进入x[j]子树//x[i-1]与x[j]连通使得1-i层连成一条路径且累计花费优于目前最优值//可以进入x[j]子树//这里要做的就是交换x[i]与x[j],进入i+1层//思想类似于n的全排列问题,递归求解//bestc=NoEdge说明还没有广搜到一条回路,那就先试着求出一个可行解//现在的解是x[1],x[2]...x[i]...x[j]...x[n]if(a[x[i-1]][x[j]]!=NoEdge&&cc+a[x[i-1]][x[j]]<bestc||bestc==NoEdge){//满足条件,可以交换//交换之后,现在的解是x[1],x[2]...x[j]...x[i]...x[n]swap(x[i],x[j]);//现在的解是x[1],x[2]...x[j]...x[i]...x[n]//此时第i个元素是==x[j]//第j个元素是==x[i]cc=cc+a[x[i-1]][x[i]];//更新路径的长度,进入i+1层getmincost(i+1);cc=cc-a[x[i-1]][x[i]];//还原路径的长度,比较x[j+1]子树swap(x[i],x[j]);//还原之前的解//现在的解是x[1],x[2]...x[i]...x[j]...x[n]}}}return ;
}
int main()
{cin>>n;//输入顶点个数int k;memset(a,NoEdge,sizeof(a));cin>>k;//输入边的个数;int p,q,len;//初始化邻接矩阵for(int i=1;i<=k;i++){cin>>p>>q>>len;a[p][q]=a[q][p]=len;}//初始化最优解for(int i=1;i<=n;i++)bestx[i]=x[i]=i;//初始化最优值bestc=NoEdge;cc=0;//初始化当前值为0getmincost(2);//出发点已知cout<<bestc<<endl;for(int i=1;i<=n;i++)cout<<bestx[i]<<" ";cout<<1;}
/*
4
6
1 2 30
1 3 6
1 4 4
2 3 5
2 4 10
3 4 20
*/

(2)分支界限求解TSP问题

#include<iostream>
#include<cstring>
#include<queue>
#define INF 1e7
#define N 10
using namespace std;
struct node
{int cl;//当前走过的路径长度int id;//处理的第几个景点int x[N];//记录当前路径node() {}node(int c,int i){cl=c;id=i;memset(x,0,sizeof(x));}
};int m[N][N];//邻接矩阵存储无向带权图
int bestx[N];//最优解路径
int bestl;//最优解长度
int n,M;//景点数目,路径数目
void init()
{int i,j;for(i=0; i<N; ++i)for(j=0; j<N; ++j)m[i][j]=INF;memset(bestx,0,sizeof(bestx));bestl=INF;
}
struct cmp
{bool operator() (node n1,node n2)//当前路径长度短的优先级高{return n1.cl>n2.cl;//最小堆}
};
void bfs()
{priority_queue<node,vector<node>,cmp> q;node temp(0,2);//起点已经确定,从第2个景点开始int t;for(int i=1; i<=n; ++i)temp.x[i]=i;//初始化解向量q.push(temp);node live;//活结点while(!q.empty()){live=q.top();q.pop();t=live.id;if(t==n)//处理到倒数第二个景点{if(m[live.x[t-1]][live.x[t]]!=INF&&m[live.x[t]][1]!=INF)//满足约束条件,有路径{if(live.cl+m[live.x[t-1]][live.x[t]]+m[live.x[t]][1]<bestl)//更新最优解{bestl=live.cl+m[live.x[t-1]][live.x[t]]+m[live.x[t]][1];for(int i=1; i<=n; ++i)bestx[i]=live.x[i];}}continue;}if(live.cl>=bestl)//不满足限界条件continue;for(int j=t; j<=n; ++j) //排列树,j不能定义为整个函数的局部变量,循环过程中会出现混乱{if(m[live.x[t-1]][live.x[j]]!=INF&&live.cl+m[live.x[t-1]][live.x[j]]<bestl)//满足约束条件和限界条件{temp=node(live.cl+m[live.x[t-1]][live.x[j]],t+1);for(int k=1; k<=n; ++k)temp.x[k]=live.x[k];swap(temp.x[t],temp.x[j]);q.push(temp);}}}
}
void output()
{cout<<"最短路径长度为:"<<bestl<<endl;cout<<"最短路径为:";for(int i=1; i<=n; ++i)cout<<bestx[i]<<"-->";cout<<bestx[1]<<endl;
}
int main()
{init();cout<<"请输入景点数目:";cin>>n;cout<<"请输入路径数目:";cin>>M;cout<<"请输入景点间的路径:";int i,a,b,c;for(i=0; i<M; ++i){cin>>a>>b>>c;if(c<m[a][b]){m[a][b]=c;//注意存成对称阵m[b][a]=c;}}bfs();output();return 0;
}

五.程序运行结果

(1) 回溯法求解TSP问题运行结果:

输入第一行为一个整数n,表示图的顶点数
输入第二行为一个整数k,表示图的边数
输入第3到k+3-1行表示边的信息,每一行三个数,分别表示顶点i,顶点j,i到j的路径长度a[i][j]

(2) 分支界限求解TSP问题运行结果

六.实验结果分析

时间复杂度复杂度分析:
回溯法的时间复杂度为O(n!),分支界限的时间复杂度为O(2^n);从实验结果可以看出来分支限的时间相对来说较少。
结果分析:


由于只有4个城市,如果规定售货员总是从城市1出发,那么依据排列组合可以得到6种不同的旅行方案,比如12341、13241等等。在这些排列组合基础上可以很容易绘制出一棵排列树,也是该问题的解空间树,排列树如下:

回溯法过程:

分支限界过程图:

七.结论

通过此实验我发现回溯法有点类似于暴力枚举的搜索过程,回溯法的基本思想是按照深度优先搜索的策略,从根节点出发深度搜索解空间树,当搜索到某一节点时,如果该节点可能包含问题的解,则继续向下搜索;反之回溯到其祖先节点,尝试其他路径搜索。而分支限界法是利用广度优先搜索的策略或者以最小耗费(最大效益)优先的方式搜索问题的解空间树,对于解空间树中的活节点只有一次机会成为拓展节点,活节点一旦成为扩展节点,那么将一次性产生其所有儿子节点。
对于优先队列式的分支限界法,这些儿子节点中,不可行解或者一定不能成为最优解的儿子节点会被舍弃,其余儿子节点将会按照优先级依次存入一个活节点表(队列),此后会挑出活节点表优先级最高的节点作为下次扩展节点,重复此过程,直至找到问题的最优解。

回溯法和分支限界法解决旅行商问题相关推荐

  1. 五大经典算法(贪婪、动态规划、分治、回溯、分支限界法)及其联系和比较

    一.贪心法 贪心算法的含义: 贪心算法(也叫贪婪算法)是指在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解.贪心算法不是对所有问题都能得 ...

  2. 0/1背包问题——动态规划、回溯、分支限界法对比

    0/1背包问题--动态规划.回溯.分支限界法对比 2017.12.19 20:42:02 字数 3713 阅读 2820 目录 1.问题描述 1.1 问题描述 1.2 问题的数学表示(规划类问题,此种 ...

  3. 分支限界法的旅行商问题

    分支限界法的旅行商问题(TSP) 旅行推销员问题( 英语:Travelling salesman problem, TSP)是这样一个问题:给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并 ...

  4. C++~回溯+贪心法解决01背包问题

    C++~回溯+贪心法解决01背包问题 参考文章: (1)C++~回溯+贪心法解决01背包问题 (2)https://www.cnblogs.com/rimochiko/p/8168638.html 备 ...

  5. 遗传算法解决旅行商问题(TSP)

    遗传算法解决旅行商问题(TSP) 参考文章: (1)遗传算法解决旅行商问题(TSP) (2)https://www.cnblogs.com/studylyn/p/5097238.html 备忘一下.

  6. GitHub#python#:用自组织映射解决旅行商问题

    项目名称:som-tsp:用自组织映射解决旅行商问题 (2018年1月21日发布,作者:Diego Vicente) 项目地址: GitHub地址:https://github.com/DiegoVi ...

  7. 分支限界法解决01背包问题

    分支限界法解决01背包问题 参考文章: (1)分支限界法解决01背包问题 (2)https://www.cnblogs.com/yanyu01/p/9075704.html 备忘一下.

  8. 解决旅行商问题的方法

    旅行商问题 旅行商问题 定义: 简介: 智能优化算法解决旅行商问题 遗传算法 TSP问题基本概念 遗传算法的基本原理 1.设计算法的编码方式 2.设计遗传算法的适应度函数 3.设计遗传算法的选择操作 ...

  9. 利用分支限界法解决01背包和货郎担问题

    利用分支限界法解决01背包和货郎担问题详细步骤 原文链接:https://www.cnblogs.com/chihaoyuIsnotHere/p/10007086.html

最新文章

  1. 前端面试题:算法-冒泡排序
  2. mysql xplugin_mysql 5.7.12 新增 X plugin 详解
  3. python编程和c语言编程的区别-通过实例浅析Python对比C语言的编程思想差异
  4. 唯一分解定理(算术基本定理)详解——hdu5248和lightoj1341
  5. C#中使用Oracle 存储过程笔记
  6. 华为鸿蒙麒麟玉兔_华为P50除了麒麟9000,还预装鸿蒙系统,比iPhone12值得买
  7. c语言 已知某系统在通信联络中,数据结构(习题)..doc
  8. python搭建selenium_自动化测试之路3-selenium3+python3环境搭建
  9. 做女程序员是一种什么样的体验?
  10. Maven项目缺少Maven Dependencies解决方法
  11. 四川大学计算机学院琚生根教授,基于卷积神经网络和自注意力机制的文本分类模型...
  12. jersery集成jackson实现restful api,由于jdk版本不一致导致的坑
  13. python pywifi模块——暴力破解wifi
  14. deb 中标麒麟_注意:银河麒麟和中标麒麟不是同一个操作系统
  15. html如何退出登录,微信小程序怎么退出登录
  16. vue-meta实现router动态设置meta标签
  17. win7 变wifi热点
  18. C++实现进程通信(管道pipe)
  19. 卷积神经网络流程图_AAAI 2020 | 北大:图卷积中的多阶段自监督学习算法
  20. 违反卖家评论政策被警告了应该怎么操作?

热门文章

  1. Python+tkinter+Treeview模拟表格并设置字体和颜色
  2. 全志a10 Android,全志A10 android平台CVBS效果调试
  3. abe.jar工具的安装
  4. label smooth/mixup——深度学习中的一种防止过拟合方法
  5. Apollo项目实战
  6. “第二盖茨”——马克·扎克伯格
  7. 华为OD机试 - 相对开音节 | 备考思路,刷题要点,答疑 【新解法】
  8. 开年纳新|天空卫士家族荣誉谱上再添多名新“成员”
  9. 字节跳动创始人张一鸣演讲
  10. moments音标_at the moment