实验三 旅行商问题
一. 实验内容
运用分别编程实现回溯法和分支限界法求 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 ++;//前进到更深层搜索
else
t --; //回溯到上一层的活结点
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