类似于回溯法,也是一种在问题的解空间树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[][]等于的权值,若没有边,则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]不为无穷,即能走通,否则继续寻找能走通的路。队列不为空。
(2)限界条件: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]]
//分支限界:利用广度优先点
void bfs(){
priority_queueq;
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) continue;//限界条件·
for( j=t;j<=n;j++){//扩展所有分支
if(newnode. cl+maps[newnode.x[t-1]][newnode.x[j]]
本题中:(1):时间复杂度:最坏情况下,每一层有n-i个结点,有1+n+n(n-1)…
(n-1)(n-2)(n-3)…2
(2)空间复杂度:每个结点都有一个解向量数组x[],占用空间O(n),结点个数最坏为O(n!),所以该算法的空间复杂度为O(n*n!)。
分支限界法常以广度优先或最小耗费(最大收益)优先的方式搜索空间树。在分支限界算法中,每个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子节点,在这些儿子节点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重负上述结点扩展过程。这个国政一致持续到找到所需的解或活结点表为空时为止。
(1)单源最短路径问题
(2)装载问题
(3)布线问题
(4)0-1背包问题
#include
#include
#include
#include
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_queueq;
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) continue;//限界条件·
for(int j=t;j<=n;j++){//扩展所有分支
if(newnode. cl+maps[newnode.x[t-1]][newnode.x[j]]>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;
}