旅行商问题

        在无向带权图G=(V,E)中,结点代表景点,直接到达的景点有边相连,边上的数字代表景点之间的路径长度。找出从出发地开始的一个景点排列,按照这个顺序不重复地走遍所有景点回到出发地,所经过的路径长度最短。

        f1559d2b2e2549d5ae0a40a4ace71362.png

        (1)定义问题的解空间

        解的形式为n元组:{x1,x2,…,xi,…,xn},分量xi表示第i个要去的旅游景点编号,景点的集合为S={1,2,…,n}。因为景点不可重复走,因此在确定xi时,前面走过的景点{x1,x2,…,xi-1}不可以再走,xi的取值为S−{x1,x2,…,xi-1},i=1,2,…,n。 

        (2)解空间的组织结构

        问题解空间是一棵排列树,树的深度为n=4。

        86ba544719d249ffb60205a0a651bb9c.png

        (3)搜索解空间

         ·    约束条件

        用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]≠∞表示城市i和城市j有边相连,能走通。

        ·    限界条件

         cl<bestl,cl的初始值为0,bestf的初始值为+∞。

         cl:当前已走过的城市所用的路径长度。

        bestl:表示当前找到的最短路径的路径长度。 

        ·    搜索过程

         如果采用普通队列式的分支限界法,除了最后一层外,所有的结点都会生成,普通队列式的分支限界法是不可行的。

        可以使用优先队列式分支限界法,提高搜索速度。设置优先级:当前已走过的城市所用的路径长度cl。cl越小,优先级越高。

         从根结点开始,以广度优先的方式进行搜索。根节点首先成为活结点,一次性生成所有孩子结点,判断孩子结点是否满足约束条件和限界条件,如果满足,则将其加入队列中;反之,舍弃。然后再从队列中取出一个元素,作为当前扩展结点,搜索过程队列为空时为止。

        从1号景点出发,经过所有景点一次且只有一次,求最短路径。

        9286cc1d7785489a901660b158ed71b1.png

        a31ab291be3c49bf8964e28547c597b1.png 

        (1)时间复杂度

         最坏情况下,除了最后一层外,有1+n+n(n−1) +…+ (n−1)(n−2)…2≤n(n−1)!个结点需要判断约束函数和限界函数,判断两个函数需要O(1)的时间,因此耗时O(n!),时间复杂度为O(n!)。

        (2)空间复杂度

         程序中我们设置了每个结点都要记录当前的解向量x[]数组,占用空间为O(n),结点的个数最坏为O(n!),所以该算法的空间复杂度为O(n*n!)。 

        算法优化拓展

        算法开始时创建一个用于表示活结点优先队列。每个结点的费用下界zl=cl+rl值作为优先级。cl表示已经走过的路径长度,rl表示剩余路径长度的下界,rl用剩余每个结点的最小出边之和来计算。初始时先计算图中每个顶点i的最小出边并用minout[i]数组记录,minsum记录所有结点的最小出边之和。如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法立即结束。

        (1)约束条件

         用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]≠∞表示城市i和城市j有边相连,能走通。

        (2)限界条件

        zl<bestl。

        zl=cl+rl。

        cl:当前已走过的城市所用的路径长度。

         rl:当前剩余路径长度的下界。

         bestl:当前找到的最短路径的路径长度。

        (3)优先级

        设置优先级:zl指已经走过的路径长度+剩余路径长度的下界。zl越小,优先级越高。

        48eff1bea14c40cb9611bf105a79819d.png

        

//program 6.3 旅行商问题 优先队列式分支限界法 
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=1e7;
const int N=105;
int g[N][N];
int bestx[N]; //当前最优路径
int bestl; //当前最短路径长度
int n,m;  //结点数,边数

struct node{
    int cl; //当前已走过的路径长度
    int id; //当前序号
    int x[N];//当前路径
    node() {}
    node(int _cl,int _id){
        cl=_cl;
        id=_id;
    }
};

bool operator <(const node &a, const node &b){//优先队列的优先级,cl值越小越优先
    return a.cl>b.cl;
}

void traveling_prioritybfs(){//优先队列式分支限界法
    priority_queue<node> q; //创建一个优先队列
    node newnode=node(0,2);//创建根节点
    for(int i=1;i<=n;i++)
    	newnode.x[i]=i; //初时化新结点解向量
    q.push(newnode);//根结点入队
    while(!q.empty()){
        node cur=q.top();//取队头
        q.pop(); //队头出队
        int t=cur.id;//当前处理的序号
        if(t==n){//搜到倒数第2个结点时,判断是否更新最优解,
            //例如当前找到一个路径(1243),到达4号结点时,立即判断g[4][3]和g[3][1]是否有边相连,
            //如果cl+g[4][3]+g[3][1]<bestl,则更新最优值和最优解
        	if(cur.cl+g[cur.x[n-1]][cur.x[n]]+g[cur.x[n]][1]<bestl){
            	bestl=cur.cl+g[cur.x[n-1]][cur.x[n]]+g[cur.x[n]][1];
                for(int i=1;i<=n;i++)
                	bestx[i]=cur.x[i];
        	}
            continue;
        }
    	if(cur.cl>=bestl)//判断当前结点是否满足限界条件,如果不满足不再扩展
        	continue;
        for(int j=t;j<=n;j++){//排列树 
            if(g[cur.x[t-1]][cur.x[j]]!=inf){//如果x[t-1]与x[j]有边相连
                int cl=cur.cl+g[cur.x[t-1]][cur.x[j]];
                if(cl<bestl){//有可能得到更短的路线
                    newnode=node(cl,t+1);
                    for(int i=1;i<=n;i++)
                    	newnode.x[i]=cur.x[i];//复制以前的解向量
                    swap(newnode.x[t],newnode.x[j]);//交换x[t]、x[j]两个元素的值
                    q.push(newnode);//新结点入队
                }
            }
        }
    }
}

void init(){//初始化
    bestl=inf;
    memset(g,0x3f,sizeof(g));
    memset(bestx,0,sizeof(bestx));
}

void print(){//打印路径
    cout<<"最短路径:  ";
    for(int i=1;i<=n;i++)
        cout<<bestx[i]<<"-->";
    cout<<"1"<<endl;
}

int main(){
    int t;//测试用例数 
	int u,v,w;//u,v代表结点,w代表u和v之间路的长度;
    cin>>t;
    while(t--){
    	cin>>n>>m;
    	init();
	    for(int i=1;i<=m;i++){
	        cin>>u>>v>>w;
	        g[u][v]=g[v][u]=w;
	    }
    	traveling_prioritybfs();
    	cout<<bestl<<endl;
//    	print();
    }
    return 0;
}
/*测试数据
1
4 6
1 2 15
1 3 30
1 4 5
2 3 6
2 4 12
3 4 3
*/ 

 

//program 6.3 旅行商问题 回溯法 
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=1e7;
const int N=105;
int g[N][N];
int x[N],bestx[N]; //记录当前路径,当前最优路径
int cl,bestl; //当前路径长度,当前最短路径长度
int n,m;     //城市个数n,边数m

void traveling(int t){//回溯法 
    if(t>n){ //到达叶子结点
        if(g[x[n]][1]!=inf&&(cl+g[x[n]][1]<bestl)){
            for(int j=1;j<=n;j++)//记录最优解 
            	bestx[j]=x[j];
            bestl=cl+g[x[n]][1];//记录最优值
        }
        return; 
    }
    for(int j=t;j<=n;j++){
        if(g[x[t-1]][x[j]]!=inf&&(cl+g[x[t-1]][x[j]]<bestl)){
            swap(x[t],x[j]);//交换两个元素的值
            cl=cl+g[x[t-1]][x[t]];
            traveling(t+1);
            cl=cl-g[x[t-1]][x[t]];//还原现场
            swap(x[t],x[j]);//复位 
        }
    }
}

void init(){//初始化
    bestl=inf;
    cl=0;
    memset(g,0x3f,sizeof(g));
    memset(bestx,0,sizeof(bestx));
    for(int i=0;i<=n;i++)
        x[i]=i;
}

void print(){//打印路径
    cout<<"最短路径:  ";
    for(int i=1;i<=n;i++)
        cout<<bestx[i]<<"--->";
    cout<<"1"<<endl;
}

int main(){
    int t;//测试用例数 
	int u,v,w;//u,v代表结点,w代表u和v之间路的长度;
    cin>>t;
    while(t--){
    	cin>>n>>m;
    	init();
	    for(int i=1;i<=m;i++){
	        cin>>u>>v>>w;
	        g[u][v]=g[v][u]=w;
	    }
    	traveling(2);
    	cout<<bestl<<endl;
    	//print();
    }
    return 0;
}
/*测试数据
1
5 9
1 2 3
1 4 8
1 5 9
2 3 3
2 4 10
2 5 5
3 4 4
3 5 3
4 5 20
*/ 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值