分支与限界-旅行售货员问题

算法描述

类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。

分支搜索算法

所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。
选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式。
1)FIFO搜索
2)LIFO搜索
3)优先队列式搜索

分支限界搜索算法

分支限界法的一般过程:
由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。
分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。

问题描述

旅行家要旅行n个城市,已知各城市之间的路程。要求选定一条从驻地出发经过每个城市一次,最后回到驻地的路线,使总的路程最小。例如:从如图中的无向带权图G=(V,E)中的1号点出发,经过图中的每个点,并且不能重复,然后回到1号点,要求经过的路径长度是最短的(即求图中 路径 )。

在这里插入图片描述

相关概念和数据结构的介绍

旅行售货员问题的解空间可以组织成一棵树,从树的根结点到任一叶结点的路径定义了图的一条周游路线。旅行售货员问题要在图G中找出费用最小的周游路线。路线是一个带权图。图中各边的费用(权)为正数。图的一条周游路线是包括V中的每个顶点在内的一条回路。周游路线的费用是这条路线上所有边的费用之和,树的深度为n(即节点数的大小)。
定义解向量s={x[]={}};x[i]表示第i个要去的地点编号。景点不能重复走,因此,走过的景点{x1,x2,x3,…,xi-1}都不能再走,xi的取值s-{x1,x2,x3,…xi-1}。
数据结构:
用bestw表示最优解,best[]表示最优解的路径。
设一个带权的邻接矩阵maps[][],表示从i顶点到j顶点的距离,maps[][]等于<i,j>的权值,若没有边,则maps[i][j]为无穷大。
(2).定义一个结构体,用于保存解空间树的节点;

struct Node{
    int x[N];   //解向量,从1开始,记录路径
    int cl;  //表示当前已走过的路径长度
    int id  ;//表示当前解的层数
    Node (){}
    Node(int _cl,int _id){
        cl=_cl;
        id=_id;
    }
};

算法设计

定义解空间树后,对解空间进行搜索,需要先找到限界条件和约束条件
(1)约束条件:如果带权邻接矩阵maps[i][j]不为无穷,即<i,j>能走通,否则继续寻找能走通的路。队列不为空。
(2)限界条件:cl<bestw,cl初始值为0,bestw初始值为无穷。
cl表示当前已走过的路径长度。
搜索过程:
将满足条件(即上一层节点的路径长度+上层节点到扩展节点的长度<bestw)的节点加入队列,为了加快搜索速度,使用优先队列,优先队列中的优先级为已走过的路径长度cl,cl值越小,越优先。
定义优先队列的优先级:

bool operator<(const Node &a,const Node &b){
    return a.cl>b.cl;
}

因此,进入优先队列的节点会自动进行一次排序,将最接近最优解的结点排到最前面。
解空间树:

在这里插入图片描述
排列树图解
(1)创建初始节点A0, 假设从第1号结点开始走,x[1]=1,生成第二层的结点,A(cl,id),Cl=0,id=2(id表示第解空间树的第id层);x[]={1,2,…,n};将A加入优先队列。定义临时变量t表示当前搜索解空间的层数。
(2)扩展A结点,搜索A的所有分支,for(j=t;j<=n;j++)判断x[t-1]与x[j]是否相连,且cl+maps[x[i-1]][x[j]]<bestw;如果满足则生成新结点,并更新新节点的cl =cl+maps[x[i-1]][x[j]], id=t+1;复制父节点A的解向量,并执行交换操作swap(x[t],x[j]),将生成的新结点加入优先队列。如不满足,则进行下一次搜索。
(3)当t=n时,表示搜索到最后一层,此时不仅要判断cl+maps[x[i-1]][x[j]]<bestw,而且要判断x[j]是否和x[1]连通,若连通,则进一步判断: node.cl+maps[node.x[t-1]][ node.x[n]]+maps[node.x[n]][node.x[1]]<bestw,若满足则更新最优解bestw的值,并且用best[]复制x[]的值,保存当前最优解。当队列不为空时还需进行(2)中的操作。
主要代码:

//分支限界:利用广度优先点
void bfs(){
    priority_queue<Node>q;
    Node node;
    node=Node(0,2);
	   int i,j;
    for( i=1;i<=n;i++){
        node.x[i]=i;
    }
    q.push(node);
    while(!q.empty()){
        Node newnode=q.top();
        q.pop();
        int t;
        t=newnode.id;//当前层数
        if(t==n){
            if(maps[newnode.x[t-1]][newnode.x[n]]!=INF && maps[newnode.x[n]][newnode.x[1]]!=INF){   / /判断上层结点与扩展结点并且扩展结点与起始结点是否连通
if(newnode.cl+maps[newnode.x[t-1]][newnode.x[n]]+
maps[newnode.x[n]][newnode.x[1]]<bestw){
/ /判断上层结点与扩展结点并且扩展结点与起始结点的路径长度是否小于当前最优路径长度

         bestw= newnode.cl+maps[newnode.x[t-1]][newnode.x[n]]
                   +maps[newnode.x[n]][newnode.x[1]];//更新bestw;
         for(i=1;i<=n;i++)
             best[i]=newnode.x[i];  //更新结点的解向量即保存最优路径
                }
            }
        }
   if(newnode.cl>=bestw) continue;//限界条件·
      for( j=t;j<=n;j++){//扩展所有分支
      if(newnode. cl+maps[newnode.x[t-1]][newnode.x[j]]<bestw){
          int c=newnode.cl+maps[newnode.x[t-1]][newnode.x[j]];
           / /更新路径长度
           node=Node(c,t+1);//生成新结点
             for( i=1;i<=n;i++)
                 node.x[i]=newnode.x[i];
                swap(node.x[t],node.x[j]);
               q.push(node);
            }
        }
    }
}

算法分析

本题中:(1):时间复杂度:最坏情况下,每一层有n-i个结点,有1+n+n(n-1)…
(n-1)(n-2)(n-3)…2<n(n-1);时间复杂度为O(n!).
(2)空间复杂度:每个结点都有一个解向量数组x[],占用空间O(n),结点个数最坏为O(n!),所以该算法的空间复杂度为O(n*n!)。
分支限界法常以广度优先或最小耗费(最大收益)优先的方式搜索空间树。在分支限界算法中,每个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子节点,在这些儿子节点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重负上述结点扩展过程。这个国政一致持续到找到所需的解或活结点表为空时为止。

应用举例

(1)单源最短路径问题
(2)装载问题
(3)布线问题
(4)0-1背包问题

实验结果

在这里插入图片描述
在这里插入图片描述

代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const  int INF=0xfffffff;
const int  N=100;
int maps[N][N];   //存储图
int n,m;         //n表示顶点数,m表示边数
int best[N];     //保存最优路径
int bestw;
struct Node
{
    int x[N];    //解向量,方便从1开始,记录路径
    int cl;      //表示当前已走过的路径长度
    int id;     //表示层数
    Node (){}
    Node(int _cl,int _id){
        cl=_cl;
        id=_id;
    }
};
bool operator<(const Node &a,const Node &b){
    return a.cl>b.cl;
}
void bfs()
{
    priority_queue<Node>q;
    Node node;
    node=Node(0,2);
    for(int i=1;i<=n;i++){
        node.x[i]=i;
    }
    q.push(node);
    while(!q.empty()){
        Node newnode=q.top();
        q.pop();
        int t;
        t=newnode.id;//当前层数
        if(t==n){
            if(maps[newnode.x[t-1]][newnode.x[n]]!=INF && maps[newnode.x[n]][newnode.x[1]]!=INF){
             if(newnode.cl+maps[newnode.x[t-1]][newnode.x[n]]
                   +maps[newnode.x[n]][newnode.x[1]]<bestw){
                   bestw= newnode.cl+maps[newnode.x[t-1]][newnode.x[n]]
                   +maps[newnode.x[n]][newnode.x[1]];//更新bestw;
                   for(int i=1;i<=n;i++)
                    best[i]=newnode.x[i];
                }
            }
        }
        if(newnode.cl>=bestw) continue;//限界条件·
        for(int j=t;j<=n;j++){//扩展所有分支
            if(newnode. cl+maps[newnode.x[t-1]][newnode.x[j]]<bestw){
                int c=newnode.cl+maps[newnode.x[t-1]][newnode.x[j]];
                 node=Node(c,t+1);
                for(int i=1;i<=n;i++)
                    node.x[i]=newnode.x[i];
                swap(node.x[t],node.x[j]);
               q.push(node);
            }
        }
    }

}
void print(){
    for(int i=1;i<=n;i++)
        cout<<best[i]<<" ";
    cout<<best[1]<<endl;
    cout<<"最优解:"<<bestw;
}
int main(){
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
          maps[i][j]=INF;
    int u,v,w;
    cout<<"输入顶点和边数:\n";
    cin>>n>>m;
    cout<<"输入每条边的信息:";
    for(int i=1;i<=m;i++){
        cin>>u>>v>>w;
        maps[u][v]=maps[v][u]=w;
    }
    bestw=INF;
    bfs();
    print();
    return 0;
}
  • 18
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分支限界法是一种用于求解组合优化问题的算法,可以用来解决旅行售货员问题(TSP,Traveling Salesman Problem)。TSP是一个经典的NP困难问题,目标是寻找一条路径,使得旅行售货员能够经过所有城市并且总路程最短。 下面是使用分支限界法求解TSP的实验说明: 1. **问题建模**:将TSP问题转化为图论问题。将城市视为图的节点,城市间的距离视为图的边权重。 2. **初始界限计算**:计算初始上界(UB)和初始下界(LB)。UB可以设置为一个较大的值,而LB可以通过启发式算法(如最小生成树算法)得到。 3. **状态空间树构建**:构建状态空间树,并根据约束条件进行剪枝。状态空间树的每个节点表示一个可能的路径。 4. **分支规则**:选择一个节点进行分支分支规则可以根据不同策略选择下一个要探索的节点,例如选择下一个未访问的城市进行扩展。 5. **子问题求解**:对每个分支节点,求解子问题。子问题是在当前路径基础上添加一个城市,并计算新的上界和下界。 6. **剪枝策略**:根据上界和下界进行剪枝。如果某个分支节点的下界大于当前已知的最优解(UB),则可以剪枝。 7. **回溯和更新最优解**:在搜索过程中,记录当前最优解,并在搜索到叶子节点或者剪枝时进行更新。 8. **迭代搜索**:重复进行分支、求解子问题、剪枝等步骤,直到找到最优解或者搜索空间被完全搜索。 9. **结果输出**:输出找到的最优解路径和总路程。 需要注意的是,TSP是一个NP困难问题,随着城市数量的增加,问题的规模将呈指数级增长,求解时间会非常长。因此,在实际应用中,通常采用启发式算法等近似方法来求解TSP问题。 希望以上实验说明对您有所帮助,如果您有任何进一步的问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值