分支限界法经典案例算法分析
第6章 分支限界法(1-例子)

6.3 装载问题
3. 算法的改进
// 检查右儿子结点 // 检查左儿子结点 Type wt = Ew + w[i]; if (wt <= c) { // 可行结点 右儿子剪枝
if (Ew + r > bestw && i < n) Q.Add(Ew); // 可能含最优解 Q.Delete(Ew);// 取下一扩展结点 提前更新 bestw
5. 优先队列式分支限界法
解装载问题的优先队列式分支限界法用最大优先队列存 储活结点表。 活结点x在优先队列中的优先级定义为从根结点到结点x的 路径所相应的载重量再加上剩余集装箱的重量之和。 优先队列中优先级最大的活结点成为下一个扩展结点。 在优先队列式分支限界法中,一旦有一个叶结点成为当 前扩展结点,则可以断言该叶结点所相应的解即为最优解。 此时可终止算法。
while6.3 (true)装载问题 { // 检查左儿子结点 2. 队列式分支限界法 if (Ew + w[i] <= c) // x[i] = 1 EnQueue(Q, Ew + w[i], bestw, i, n); // 右儿子结点总是可行的 EnQueue(Q, Ew, bestw, i, n); // x[i] = 0 Q.Delete(Ew); // 取下一扩展结点 if (Ew == -1) { // 同层结点尾部 if (Q.IsEmpty()) return bestw; Q.Add(-1); // 同层结点尾部标志 Q.Delete(Ew); // 取下一扩展结点 i++; // 进入下一层 } }
if (wt > bestw) bestw = wt; // 加入活结点队列 if (i < n) Q.Add(wt); }
9——分支限界法

2 例9.1-限界
x3=1
x2=1 4
x1=1 2 x2=0 5
1
x1=0 3 x2=1 6
x2=0
x3=1 x3=0
x3=0 x3=1
x3=0 x3=1 x3=0
7
8
9
10
11 12
13
14
15
W={50,10,10},C1=60。 在此例中,结点3所在分支的所有子树中,装载货物的最 大可能是多少? 20。
20
2 例9.1-算法2 FIFO分支限界
AddLiveNode(folat wt,int i, QNode *E, int ch) { Qnode *b; if (i=n) //叶子 { if (wt>bestw) //目前的最优解 { bestE=E; bestx[n]=ch;} //bestx[n]取值为ch return; } b = new QNode; // 不是叶子, 添加到队列中 b->weight=wt; b->parent=E; b->LChild=ch; add (Q,b) ; }
3 7 14 15
分支搜索法是一种在问题解空间上进行搜索尝试的算法。 所谓“分支”是采用广度优先的策略,依次生成E-结点所 有分支,也就是所有的儿子结点。 和回溯法一样,可以在生成的结点中,抛弃那些不满足约 束条件的结点,其余结点加入活结点表。然后从表中选择 一个结点作为下一个E-结点。 选择下一个E-结点方式的不同导致几种分支搜索方式:
8
2 例9.1 装载问题
子集树
x3=1
x2=1 4
x1=1 2 x2=0 5
1
x1=0 3 x2=1 6
第6章 分支限界法

通常采用最大堆或最小堆来实现优先队列式分支限界法求解问 题。
可以用如下方法求得最优解中的分量:1)对每个扩展结点保存
该结点到根结点的路径;2)在搜索过程中构建搜索经过的树结
构,在求得最优解时,从叶子结点不断回溯到根结点,以确定最
优202解0年中7月的19日各个分量。
21
提纲
一、分支限界法的基本思想 二、单源最短路径问题 三、装载问题 四、0-1背包问题 五、最大团问题 六、旅行售货员问题
E 使总的路程最短。
23
F GH I J K 43 42 32
L MN O P Q
2020年7月19日
17
分支限界法解旅行售货员问题
FIFO队列:
活结点队列: {{F{CED}G,{,GH,FD{,E{IH,,J{,GHIJEK{F,IJK,,}KIHG,J},KJ},}KI,K}}}}
B
2
3
4
1 30 2
2020年7月19日
24
2.2单源最短路径问题算法思想
用一最小堆来存储活结点表。其优先级是结点所对应的 当前路长。
算法从图G的源顶点s和空优先队列开始。结点s被扩展 后,它的儿子结点被依次插入堆中。此后,算法从堆中 取出具有最小当前路长的结点作为当前扩展结点,并依 次检查与当前扩展结点相邻的所有顶点。如果从当前扩 展结点i到顶点j有边可达,且从源出发,途经顶点i再到 顶点j的所相应的路径的长度小于当前最优路径长度,则 将该顶点作为活结点插入到活结点优先队列中。这个结 点的扩展过程一直继续到活结点优先队列为空时为止。
[G] N, 0 =>N(25), O(0)
不搜索以不可行结点为根的子树
优先队列式分支限法:
[A] B, C => B(45), C(0)
第7章--分支限界法NEW

Algorithm
USTB
例:0-1背包问题。假设有4个物品,其重量分别为(4, 7, 5, 3),价值分别为(40, 42, 25, 12),背包容量W=10。首先,将 给定物品按单位重量价值从大到小排序,结果如下: 价值/重量 价值 重量 (v/w) 10 6 5 4
12
Algorithm Design & Analysis
Algorithm
USTB
分支限界法与回溯法的比较
(1)求解目标:回溯法的求解目标是找出解空间树 中满足约束条件的所有解,而分支限界法的求解目标 则是找出满足约束条件的一个解,或是在满足约束条 件的解中找出在某种意义下的最优解。 (2)搜索方式的不同:回溯法以深度优先的方式搜 索解空间树,而分支限界法则以广度优先或以最小耗 费优先的方式搜索解空间树。
16
Algorithm Design & Analysis
Algorithm
USTB
用分支限界法求TSP
TSP是求排列的问题,不是仅找一条路径而已。因而需要 对分支限界法的一般算法作些修改: (1)待扩展的结点如果在本路径上已经出现,则不再扩展,但 若是在其他路径上出现过,则仍需要扩展。 (2) (2)新结点,无论其优劣,既不影响其它路径上的结点,也不 受其它路径上的结点的影响。 (3)依据上界函数决定结点是否可以剪去。
17
Algorithm Design & Analysis
Algorithm
USTB
分支限界法求排列
⑴计算初始结点s的f(s); [s, f(s), nil]放入Open; ⑵while (Open ≠Φ) { ⑶ 从Open中取出[p, f(p), L]; //L是路径已有结点 ⑷ 若f(p)≥U,则抛弃该路径; ⑸ 若p是目标,则考虑修改上界函数值;否则 p ⑹ {将[p, f(p), L]放入Closed; ⑺ 在该路径上扩展结点p;对每个后继d ⑻ {计算f(d); ⑼ 若f(d)<U, 则{L = L ∪{p}; 将[d, f(d),L]依序放入Open。} }}}
售货员问题的分支限界算法设计

售货员问题的分支限界算法设计售货员问题(Traveling Salesman Problem,TSP)是一个经典的组合优化问题,其目标是找到一条最短的路径,让售货员访问每个城市一次并回到起始城市。
分支限界算法是一种常用于解决组合优化问题的方法之一,可以用于 TSP。
以下是一个简单的分支限界算法设计框架:问题定义:将售货员问题明确定义为一个具体的数学模型,包括城市之间的距离矩阵等。
状态空间树的构建:将问题表示为状态空间树,其中每个节点代表问题的一个可能状态,每个边代表状态之间的转移。
起始节点对应于售货员的起始城市。
界的计算:在每个节点上计算一个上界(可行解的上界)和一个下界(最优解的下界)。
这些界用于指导搜索。
搜索过程:使用深度优先搜索或广度优先搜索策略,通过分支和界的计算逐步构建状态空间树,直到找到最优解或搜索完整个状态空间。
分支操作:在每个节点上,生成所有可能的分支(城市的排列顺序),并计算每个分支的成本。
剪枝操作:对于某些分支,如果它们的成本已经超过已知的最优解,可以剪枝,减少搜索空间。
更新最优解:在搜索过程中,不断更新已知的最优解。
终止条件:当搜索到达树的叶子节点时,或者当已知的最优解不再被更新时,算法终止。
下面是一个简单的伪代码示例,演示了 TSP 的分支限界算法:function traveling_salesman(node, cost):if is_leaf(node):update_best_solution(cost)else:for each branch in generate_branches(node):if cost_of(branch) < current_best_solution_cost:traveling_salesman(branch, cost + cost_of(branch))在这个伪代码中,generate_branches 生成当前节点的所有可能分支,is_leaf 判断节点是否是叶子节点,cost_of(branch) 计算分支的成本。
C++用分支限界法求解最短布线问题

分支限界法类似回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但分支限界法只找出满足约束条件的一个最优解,并且以广度优先或最小耗费优先的方式搜索解空间树T。树T是一棵子集树或排列树。在搜索时,每个结点只有一次机会成为扩展结点,并且一次性产生其所有儿子结点。从活结点表中选择下一扩展结点有两种方式:(1)队列式(FIFO)(2)优先队列式。分支限界法可广泛应用于单源最短路径问题,最大团问题,布线问题,电路板排列问题等。
{
q=q->next;
}
q->next=t;
return;
}
Position outq()
{
Position out;
out.row=team_l->x;
out.col=team_l->y;
team_l=team_l->next;
return out;
}
void Find_path()
{
Position offset[4];
{
cout<<"请输入占用点的坐标(x,y): ";
cin>>x>>y;
if(x<0 || x>m+1 || y<0 || y>n+1 || (x==start.row && y==start.col) || (x==end.row && y==end.col))
{
cout<<"输入错误,请重新输入!!!\n";
{
cout<<"\n没有结果!!!\n";
分支限界法实验(单源最短路径)
算法分析与设计实验报告第七次实验姓名学号班级时间12.26上午地点工训楼309实验名称分支限界法实验(单源最短路径)实验目的1.掌握并运用分支限界法的基本思想2.运用分支限界法实现单源最短路径问题实验原理问题描述:在下图所给的有向图G中,每一边都有一个非负边权。
要求图G的从源顶点s 到目标顶点t之间的最短路径。
基本思想:下图是用优先队列式分支限界法解有向图G的单源最短路径问题产生的解空间树。
其中,每一个结点旁边的数字表示该结点所对应的当前路长。
为了加速搜索的进程,应采用有效地方式选择活结点进行扩展。
按照优先队列中规定的优先级选取优先级最高的结点成为当前扩展结点。
catch (int){break;}if(H.currentsize==0) //优先队列空{break;}}}上述有向图的结果:测试结果附录:完整代码(分支限界法)Shorest_path.cpp//单源最短路径问题分支限界法求解#include<iostream>#include<time.h>#include<iomanip>#include"MinHeap2.h"using namespace std;template<class Type>class Graph //定义图类{friend int main();public:void shortest_path(int); private:int n, //图的顶点数*prev; //前驱顶点数组Type **c, //图的邻接矩阵*dist; //最短距离数组};template<class Type>class MinHeapNode //最小堆中的元素类型为MinHeapNode{friend Graph<Type>;public:operator int() const{return length;}private:int i; //顶点编号Type length; //当前路长};//单源最短路径问题的优先队列式分支限界法template<class Type>void Graph<Type>::shortest_path(int v){MinHeap<MinHeapNode<Type>> H(1000);//定义最小堆的容量为1000//定义源为初始扩展结点MinHeapNode<Type> E;//初始化源结点E.i=v;E.length=0;dist[v]=0;while(true)//搜索问题的解空间{for(int j=1;j<=n;j++)if((c[E.i][j]!=0)&&(E.length+c[E.i][j]<dist[j])){//顶点i到顶点j可达,且满足控制约束//顶点i和j之间有边,且此路径小于原先从源点i到j的路径长度dist[j]=E.length+c[E.i][j];//更新dist数组prev[j]=E.i;//加入活结点优先队列MinHeapNode<Type> N;N.i=j;N.length=dist[j];H.Insert(N);//插入到最小堆中}try{H.DeleteMin(E); // 取下一扩展结点}catch (int){break;}if(H.currentsize==0)//优先队列空{break;}}}int main(){int n=11;int prev[12]={0,0,0,0,0,0,0,0,0,0,0,0};//初始化前驱顶点数组intdist[12]={1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000 };//初始化最短距离数组cout<<"单源图的邻接矩阵如下:"<<endl;int **c=new int*[n+1];for(int i=1;i<=n;i++) //输入图的邻接矩阵{c[i]=new int[n+1];for(int j=1;j<=n;j++){cin>>c[i][j];}}int v=1; //源结点为1Graph<int> G;G.n=n;G.c=c;G.dist=dist;G.prev=prev;clock_t start,end,over; //计算程序运行时间的算法start=clock();end=clock();over=end-start;start=clock();G.shortest_path(v);//调用图的最短路径查找算法//输出从源结点到目的结点的最短路径cout<<"从S到T的最短路长是:"<<dist[11]<<endl;for(int i=2;i<=n;i++)//输出每个结点的前驱结点{cout<<"prev("<<i<<")="<<prev[i]<<" "<<endl;}for(int i=2;i<=n;i++) //输出从源结点到其他结点的最短路径长度{cout<<"从1到"<<i<<"的最短路长是:"<<dist[i]<<endl;}for(int i=1;i<=n;i++) //删除动态分配时的内存{delete[] c[i];}delete[] c;c=0;end=clock();printf("The time is %6.3f",(double)(end-start-over)/CLK_TCK); //显示运行时间cout<<endl;system("pause");return 0;}MinHeap.h#include<iostream>template<class Type>class Graph;template<class T>class MinHeap //最小堆类{template<class Type>friend class Graph;public:MinHeap(int maxheapsize=10); //构造函数,堆的大小是10~MinHeap(){delete[] heap;} //最小堆的析构函数int Size() const{return currentsize;} //Size()返回最小堆的个数T Max(){if(currentsize) return heap[1];} //第一个元素出堆MinHeap<T>& Insert(const T& x); //最小堆的插入函数MinHeap<T>& DeleteMin(T& x); //最小堆的删除函数void Initialize(T x[],int size,int ArraySize); //堆的初始化void Deactivate();void output(T a[],int n);private:int currentsize,maxsize;T *heap;};template<class T>void MinHeap<T>::output(T a[],int n) //输出函数,输出a[]数组的元素{for(int i=1;i<=n;i++)cout<<a[i]<<" ";cout<<endl;}template<class T>MinHeap<T>::MinHeap(int maxheapsize){maxsize=maxheapsize;heap=new T[maxsize+1]; //创建堆currentsize=0;}template<class T>MinHeap<T>& MinHeap<T>::Insert(const T& x){if(currentsize==maxsize) //如果堆中的元素已经等于堆的最大大小return *this; //那么不能在加入元素进入堆中int i= ++currentsize;while(i!=1 && x<heap[i/2]){heap[i]=heap[i/2];i/=2;}heap[i]=x;return *this;}template<class T>MinHeap<T>& MinHeap<T>::DeleteMin(T& x) //删除堆顶元素{if(currentsize==0){cout<<"Empty heap!"<<endl;return *this;}x=heap[1];T y=heap[currentsize--];int i=1,ci=2;while(ci<=currentsize){if(ci<currentsize && heap[ci]>heap[ci+1])ci++;if(y<=heap[ci])break;heap[i]=heap[ci];i=ci;ci*=2;}heap[i]=y;return *this;}template<class T>void MinHeap<T>::Initialize(T x[],int size,int ArraySize) //堆的初始化{delete[] heap;heap=x;currentsize=size;maxsize=ArraySize;for(int i=currentsize/2;i>=1;i--){T y=heap[i];int c=2*i;while(c<=currentsize){if(c<currentsize && heap[c]>heap[c+1])c++;if(y<=heap[c])break;heap[c/2]=heap[c];c*=2;}heap[c/2]=y;}}template<class T>void MinHeap<T>::Deactivate(){heap=0; }。
算法设计与分析:第7章 分支限界算法
7.3.1 0/1背包问题
问题描述
• ! "$ &# $%&"# &%& # %'
– $ – $ &
%$ &!
$ "# (
算法思想
• !!3 '$6;
• 2)&!";+0#
&&E) *
.2D,<
最小代价(LC)分支限界法
代价函数!(·)
• % "!(%) %
• % "! %( % )
– %
• !(%) = ∞#
– %
• ! % =
相对代价估计函数"!($)
• "!(')((')&! • & '
• '$% &' • "!(')" *
)' )#"!(*) ≤ "!(') (
代价估计函数"!($)
• "!(') "! ' = ) ' + ,+(')
//X进队列
if(x是一个答案结点&&cost(x)<U)
//X为答案结点时修正U
if(u(x)+e < cost(x)) U=u(x)+e ;
else{ U=cost(x); ans=x;} else if(u(x)+e < U) U=u(x)+e ; //X为非答案结点时修正U
}
E.@56
_ N8!O/4/\/2i"1#9)K<iK<'- 4i ?I 40iFMZ>I 40+(104)]6=76i"/2)%PT\/3i"1#19)K<i 6iK<'- ?IY 0iFMZ>I 10]6=60i"/3)%PT\
分支限界法求布线问题
布线问题:如图1所示,印刷电路板将布线区域划分成n*m个方格。
精确的电路布线问题要求确定连接方格a的中点到b的中点的最短布线方案。
在布线时,电路只能沿直线或直角布线,如图1所示。
为了避免线路相交,已经布线的方格做了封锁标记(如图1中阴影部分),其他线路不允许穿过被封锁的方格。
3 问题的算法选择题目的要求是找到最短的布线方案,从图1的情况看,可以用贪婪算法解决问题,也就是从a开始朝着b的方向垂直布线即可。
实际上,再看一下图2,就知道贪婪算法策略是行不通的。
因为已布线的放个没有规律的所以直观上说只能用搜索方法去找问题的解。
根据布线方法的要求,除边界或已布线处,每个E-结点分支扩充的方向有4个:上、下、左、右,也就是说,一个E-结点扩充后最多产生4个活结点。
以图2的情况为例,图的搜索过程如图3所示。
搜索以a为第一个E-结点,以后不断扩充新的活结点,直到b结束(当然反之也可以)。
反过来从b到a,按序号8-7-6-5-4-3-2-1就可以找到最短的布线方案。
从图3中也可以发现最短的布线方案是不唯一的。
且由此可以看出,此问题适合用分支限界搜索。
#include <stdio.h>#include <stdlib.h>typedef struct Position{int row;int col;}Position;typedef struct team{int x;int y;struct team *next;}team,*TEAM;Position start,end,path[100];TEAM team_l=NULL;int a[100][100];int m,n,path_len;void output(){int i,j;printf("\n|-------------------布线区域图-------------------|\n");for(i=0;i<m+2;i++){for(j=0;j<n+2;j++){printf("%5d",a[i][j]);}printf("\n");}printf("|------------------------------------------------|\n");return;}void input_data(){char yes;int x,y;printf("创建布线区域...\n\n");printf("请输入区域大小(行列的个数): ");scanf("%d,%d",&m,&n);printf("请输入开始点坐标(x,y): ");scanf("%d,%d",&start.row,&start.col);printf("请输入结束点坐标(x,y): ");scanf("%d,%d",&end.row,&end.col);printf("区域内是否有被占用点? (y/n) ");fflush(stdin);scanf("%c",&yes);fflush(stdin);while(yes=='y'){printf("请输入占用点的坐标(x,y): ");scanf("%d,%d",&x,&y);fflush(stdin);if(x<0 || x>m+1 || y<0 || y>n+1 || (x==start.row && y==start.col) || (x==end.row && y==end.col)){printf("输入错误,请重新输入\n");continue;}else{a[x][y]=-1;}printf("是否还有被占用点? (y/n) ");scanf("%c",&yes);fflush(stdin);}for(x=0;x<m+2;x++){a[0][x]=-1;a[m+1][x]=-1;}for(x=0;x<n+2;x++){a[x][0]=-1;a[x][n+1]=-1;}return;}void inq(Position p){TEAM t,q;q=team_l;t=(TEAM)malloc(sizeof(TEAM));t->x=p.row;t->y=p.col;t->next=NULL;if(team_l==NULL){team_l=t;return ;}while(q->next!=NULL){q=q->next;}q->next=t;return;}Position outq(){Position out;out.row=team_l->x;out.col=team_l->y;team_l=team_l->next;return out;}void find_path(){Position offset[4];Position here={start.row,start.col};Position nbr={0,0};int num_of_nbrs=4;int i,j;offset[0].row=0;offset[0].col=1; //右offset[1].row=1;offset[1].col=0; //下offset[2].row=0;offset[2].col=-1;//左offset[3].row=-1;offset[3].col=0;//上printf("\n开始搜索路径...\n");if((start.row == end.row)&&(start.col == end.col)){ path_len = 0;return;}while(1){for(i=0;i<num_of_nbrs;i++){nbr.row=here.row+offset[i].row;nbr.col=here.col+offset[i].col;if(a[nbr.row][nbr.col]==0){a[nbr.row][nbr.col]=a[here.row][here.col] + 1;if((nbr.row == end.row) && (nbr.col == end.col)) break;inq(nbr); //nbr入队}}//是否到达目标位置finishif((nbr.row == end.row) && (nbr.col == end.col)) break;//或节点队列是否为空if(team_l==NULL){printf("\n没有结果\n");return ;}here=outq();}path_len=a[end.row][end.col];here=end;for(j=path_len-1;j>=0;j--){path[j] = here;for(i = 0;i < num_of_nbrs;i++){nbr.row = here.row + offset[i].row;nbr.col = here.col + offset[i].col;if(a[nbr.row][nbr.col] == j) //+ 2)break;}here=nbr;}return;}void out_path(){int i;printf("\n路径为:\n");printf("(%d,%d) ",start.row,start.col);for(i=0;i<path_len;i++){printf("(%d,%d) ",path[i].row,path[i].col);}printf("\n");return;}void main(){input_data();output();find_path();out_path();output(); }。
9第九章分支限界法
1
4
5
6
7
8
9
10 11
12 13 14
15 16 17
1 2 3 4 5 6 7 8 9 10 11 12 13 1415 1617
4
例9.1 4-皇后问题
2 3
1
分支-限界法 生成31个结点
4 5
6
7
8
9
10 11
12 13 14
15 16 17 B
B
B
B
B
B
18 19 20 21 B B B B 30
12
9.2.1 LC-检索的基本思想
因此: 不仅考虑结点X到一个答案结点的估计成本值ĝ(.), 还应考虑由根结点到结点X的成本h(X) 即:ĉ(X)=f(h(X))+ ĝ(X) f(.)是一个非降函数,使f(.)不等于0,可以减少算 法作偏向于纵深检查的可能性,使算法优先检索 更靠近答案结点且又离根较近的结点。
1 3 4 15 5 12 6 11 14 9 10 13 7 8 1 3 2 6 9 4 5 15 12
16
相当 庞大
2 7 8
11 14 10 13
9.2.2 15-谜问题
LESS (1)=0 LESS (4)=1 LESS (12)=6 在具体求解问题之前判定:目标状态是否在 这个初始状态的状态空间中。
8
9.1 一般方法(FIFO和LIFO分支限界)
9.2 LC-分支限界检索
9.2.1 LC检索的基本思想 9.2.2 LC检索例:15-谜 9.2.3 LC-检索的抽象化控制 9.2.4 LC-检索的特性
9.3 分支-限界算法
9
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
分支限界法
学习要点 理解分支限界法的剪枝搜索策略。 掌握分支限界法的算法框架 (1)队列式(FIFO)分支限界法 (2)优先队列式分支限界法 通过应用范例学习分支限界法的设计策略。 (1)单源最短路径问题; (2)装载问题;
(3)布线问题;
(4)0-1背包问题; (5)最大团问题;
6.1 分支限界法的基本思想
常见的两种分支限界法
(1)队列式(FIFO)分支限界法 按照队列先进先出(FIFO)原则选取下一个节点 为扩展节点。 (2)优先队列式分支限界法 按照优先队列中规定的优先级选取优先级最高的节 点成为当前扩展节点。
6.2 单源最短路径问题
1. 问题描述
下面以一个例子来说明单源最短路径问题:在下 图所给的有向图G中,每一边都有一个非负边权。要求 图G的从源顶点s到目标顶点t之间的最短路径。
6.2 单源最短路径问题
3
2 9
2 1 3
3 5
1
3
2O 2 2
目前的最短路 径是8,一旦发 现某个节点的 下界不小于这 个最短路进, 则剪枝
7 2
3 4 2
将会产生 重复的子 树,剪枝
6.2 单源最短路径问题
3
3
2 9
2 1 3
3 5
1
3
2O 2 2
利用节点 的控制关 系剪枝
6.2 单源最短路径问题
6.3 装载问题
1. 问题描述
有一批共个集装箱要装上2艘载重量分别为C1和C2的轮船, 其中集装箱i的重量为Wi,且 n
w
i 1
i
c1 c2
装载问题要求确定是否有一个合理的装载方案可将这个 集装箱装上这2艘轮船。如果有,找出一种装载方案。 容易证明:如果一个给定装载问题有解,则采用下面的 策略可得到最优装载方案。 (1)首先将第一艘轮船尽可能装满; (2)将剩余的集装箱装上第二艘轮船。
7 2 3 4 2 3 9 2 1 2 3 5 1
3 3 2 2 3
O
2
6.2 单源最短路径问题
1. 问题描述
下图是用优先队列式分支限界法解有向图G的单源 最短路径问题产生的解空间树。其中,每一个结点旁边 的数字表示该结点所对应的当前路长。 第1层
第2层
第3层 第4层 第5层
Байду номын сангаас
7 2
3 4 2 3
(6)旅行售货员问题;
6.1 分支限界法的基本思想
分支限界法与回溯法
(1)求解目标:回溯法的求解目标是找出解空间树中 满足约束条件的所有解,而分支限界法的求解目标则是 找出满足约束条件的一个解,或是在满足约束条件的解 中找出在某种意义下的最优解。 (2)搜索方式的不同:回溯法以深度优先的方式搜索 解空间树,而分支限界法则以广度优先或以最小耗费优 先的方式搜索解空间树。
6.3 装载问题
将第一艘轮船尽可能装满等 价于选取全体集装箱的一个 子集,使该子集中集装箱重 量之和最接近。由此可知, 装载问题等价于以下特殊的 0-1背包问题。 例如:
max wi xi
i 1
n
s.t. wi xi c1
i 1
n
xi {0,1},1 i n
W 10,8,5 C 16
6.1 分支限界法的基本思想
分支限界法常以广度优先或以最小耗费(最大效益) 优先的方式搜索问题的解空间树。 在分支限界法中,每一个活结点只有一次机会成为 扩展结点。活结点一旦成为扩展结点,就一次性产生其 所有儿子结点。在这些儿子结点中,导致不可行解或导 致非最优解的儿子结点被舍弃,其余儿子结点被加入活 结点表中。 此后,从活结点表中取下一结点成为当前扩展结点, 并重复上述结点扩展过程。这个过程一直持续到找到所 需的解或活结点表为空时为止。
2. 剪枝策略
在算法扩展结点的过程中,一旦发现一个结点的 下界不小于当前找到的最短路长,则算法剪去以该结 点为根的子树。
在算法中,利用结点间的控制关系进行剪枝。从 源顶点s 出发,2条不同路径到达图 G的同一顶点。由 于两条路径的路长不同,因此可以将路长长的路径所 对应的树中的结点为根的子树剪去。
6.2 单源最短路径问题
while (true) { 6.2 单源最短路径问题 for (int j = 1; j <= n; j++) if ((c[E.node][j]<inf)&&(E.length+c[E.node][j]<dist[j])) { // 顶点E.node到顶点j可达,且满足控制约束 记载 dist[j]=E.length+c[E.node][j]; 顶点i和j间有边,且此路 最短 prev[j]=E.node; 径长小于原先从原点到j 路径 的路径长 // 加入活结点优先队列 这个判断,实现了剪枝 MinHeapNode<Type> N; dist:最短距离数组 N.node=j; //顶点编号为j prev: 前驱顶点数组 N.length=dist[j]; 缺乏上界 E:当前的扩展节点 H.Insert(N); 函数减枝 c: 邻接矩阵 } H: 活节点优先队列 try {H.DeleteMin(E);} // 取下一扩展结点 catch (OutOfBounds) {break;} // 优先队列空 } 优先权队列 VS. 先进先出队列
6.3 装载问题
2. 队列式分支限界法
在算法的while循环中,首先检测当前扩展结点 的左儿子结点是否为可行结点。如果是则将其加入 到活结点队列中。然后将其右儿子结点加入到活结 点队列中(右儿子结点一定是可行结点)。2个儿子结 点都产生后,当前扩展结点被舍弃。
活结点队列中的队首元素被取出作为当前扩展 结点,由于队列中每一层结点之后都有一个尾部标 记-1,故在取队首元素时,活结点队列一定不空。 当取出的元素是 -1 时,再判断当前队列是否为空。 如果队列非空,则将尾部标记 -1加入活结点队列, 算法开始处理下一层的活结点。
3. 算法思想
解单源最短路径问题的优先队列式分支限界法用一 极小堆来存储活结点表。其优先级是结点所对应的当前 路长。 算法从图G的源顶点s和空优先队列开始。结点s被 扩展后,它的儿子结点被依次插入堆中。此后,算法从 堆中取出具有最小当前路长的结点作为当前扩展结点, 并依次检查与当前扩展结点相邻的所有顶点。如果从当 前扩展结点i到顶点j有边可达,且从源出发,途经顶点 i再到顶点j的所相应的路径的长度小于当前最优路径长 度,则将该顶点作为活结点插入到活结点优先队列中。 这个结点的扩展过程一直继续到活结点优先队列为空时 为止。