旅行商问题回溯法求解

问题描述

某售货员要到若干城市去推销商品,已知各城市之间的路程(旅费),他要选定一条从驻地出发,经过每个城市一遍,最后回到驻地的路线,使总的路程(总旅费)最小。

在这里插入图片描述

解空间

解空间:排列树
x=[1 2 3……n]
相应的排列树由x[1:n]的所有排列构成

思路

旅行商问题的解空间是一棵排列树。对于排列树的回溯搜索与生成1,2,3…n的所有排列的递归算法Perm相似。
初始的时候x=[1,2,3,…n].。
在递归算法getmincost()中,当i==n时,当前遍历到了排列树的叶子节点的上一层节点(即n-1层),这个时候要判断第n-1个点与第n个点以及第n个点与第1个点之间是否形成一条回路,如果形成一条回路,则可以判断当前回路的cost是否小于目前最优值,进而判断是否更新最优值和最优解。

当i<n的时候,还没有遍历到判断n个顶点是否形成回路。这个时候能够遍历当前节点的条件是当前节点i与上一节点i-1连通(即从第一个节点一直到第i个节点形成了一条路径),并且这条路径的长度小于当前最优值,否则不遍历当前节点(约束函数)。

输入

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

4
6
1 2 30
1 3 6
1 4 4
2 3 5
2 4 10
3 4 20

输出

输出有两行
第一行为最优值,表示旅行商的最短路径长度
第二行为最优解,为旅行商的顶点遍历序列

25
1 3 2 4 1

代码

#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;//初始化当前值为0
    getmincost(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
*/

在这里插入图片描述

  • 48
    点赞
  • 316
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
旅行商问题是一个经典的组合优化问题,它的目标是在给定的一些城市和它们之间的距离(或旅行时间)的情况下,找到一条经过每个城市一次且最短的路径。回溯法解决旅行商问题的一种常用方法。 以下是用回溯法解决旅行商问题的步骤: 1. 定义一个函数`backtrack(path, cost)`,其中`path`表示当前已经走过的路径,`cost`表示当前已经走过的路径的总旅费。 2. 如果`path`中包含了所有的城市,说明已经找到了一条完整的路径,此时更新最优解和最优值。 3. 如果`cost`已经大于当前最优值,说明当前路径不是最优解,直接返回。 4. 遍历所有的城市,如果该城市没有在`path`中出现过,将该城市加入`path`中,并将当前旅费加上该城市到上一个城市的旅费,然后递归调用`backtrack`函数。 5. 回溯到上一层时,将该城市从`path`中删除,并将当前旅费减去该城市到上一个城市的旅费。 下面是用Python实现的回溯法解决旅行商问题的代码: ```python def tsp_backtrack(graph, n): # 初始化最优解和最优值 best_path = None best_cost = float('inf') # 定义回溯函数 def backtrack(path, cost): nonlocal best_path, best_cost # 如果已经遍历完所有城市,更新最优解和最优值 if len(path) == n: cost += graph[path[-1]][path[0]] if cost < best_cost: best_path = path[:] best_cost = cost else: # 遍历所有城市 for i in range(n): if i not in path: # 将该城市加入路径中 path.append(i) cost += graph[path[-2]][i] # 递归调用回溯函数 backtrack(path, cost) # 回溯到上一层 cost -= graph[path[-2]][i] path.pop() # 从第一个城市开始遍历 path = [0] # 从第一个城市到自己的旅费为0 cost = 0 # 调用回溯函数 backtrack(path, cost) # 返回最优解和最优值 return best_path, best_cost ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值