第五章 回溯法

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

例如,对于n=3的0/1背包问题,三个物品的重量为{20, 15, 10} ,价值为 {20, 30, 25} ,背包容量为 25 。其解空间树如 下,其解空间树中的8个叶子结点分别代表该问题的8个可能 解:
1 1 2 1 3 0 6 0 5 0 9
对物品1的选择
1
10
0 3 0 12 1 14
对物品2的选择
价值=55 价值=30 价值=25
生成问题状态的基本方法
扩展结点:一个正在产生儿子的结点称为扩展结点。 活结点:一个自身已生成但其儿子还没有全部生成的节点 称做活结点。 死结点:一个所有儿子已经产生的结点称做死结点。 深度优先的问题状态生成法:如果对一个扩展结点 R ,一 旦产生了它的一个儿子 C,就把C 当做新的扩展结点。在完成 对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩 展结点,继续生成R的下一个儿子(如果存在)。 回溯法:为了避免生成那些不可能产生最佳解的问题状态, 要不断地利用限界函数(bounding function)来处死那些实际 上不可能产生所需解的活结点,以减少问题的计算量。
若用回溯法求问题的所有解时,要回溯到根, 且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的 一个解就可以结束。
5.1 回溯法的算法框架
1、问题的解空间 复杂问题常常有很多的可能解,这些可能解构成了 问题的解空间。解空间也就是进行穷举的搜索空间,所 以,解空间中应该包括所有的可能解。确定正确的解空 间很重要,如果没有确定正确的解空间就开始搜索,可 能会增加很多重复解,或者根本就搜索不到正确的解。 例如,对于有 n 个物品的 0/1 背包问题,当 n=3 时, 其解空间是: {(0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1) }
1wenku.baidu.com
4
1
7
0
8
1
0
15
对物品3的选择
11
从解空间树的根结点开始搜索,搜索过程如下: (三个物品的重量为{20, 15, 10},价值为{20, 30, 25},
背包容量为25)。
1 2 1 0 6 不可行解 1 0 8 不可行解 价值=20 1 11 10 0 12 1 14 1
1
0 9 0 13 0 15 价值=0
第五章 回溯法
回溯法
回溯法(探索与回溯法)是一种选优搜索法,按选 优条件向前搜索,以达到目标。但当探索到某一步时, 发现原先选择并不优或达不到目标,就退回一步重新 选择,这种走不通就退回再走的技术为回溯法,而满 足回溯条件的某个状态的点称为“回溯点”。 回溯法在问题的解空间树中,按深度优先策略,从 根结点出发搜索解空间树。算法搜索至解空间树的任 意一点时,先判断该结点是否包含问题的解:如果肯 定不包含,则跳过对该结点为根的子树的搜索,逐层 向其祖先结点回溯;否则,进入该子树,继续按深度 优先策略搜索。
子集树与排列树
遍历排列树需要O(n!)计算时间 遍历子集树需O(2n)计算时间
void backtrack (int t) { if (t>n) output(x); else { x[t]=1; if (legal(t)) backtrack(t+1); x[t]=0; if (legal(t)) backtrack(t+1); } void backtrack (int t) { if (t>n) output(x); else for (int i=t;i<=n;i++) { if (legal(i)) { swap(x[t], x[i]); backtrack(t+1); swap(x[t], x[i]); } }
例1:0-1背包问题:n=3, C=30, w={16, 15, 15}, v={45, 25, 25}
A B Cr=C=30,V=0 C Cr=30,V=0 w2=15,v2=25 F C =15,V=25 r
w1=16,v1=45 Cr=14,V=45
C <w Cr=14 D r 2 E V=45 不可行解
H
25<50 M 不是 Cr=14 w3=15,v3=25 最优 Cr<w3 K L J V=45 Cr=0,V=50 不可行解 解 x=(1,0,0) 50>45 I x=(0,1,1)
开始时,Cr=C=30,V=0,A为唯一活结点,也是当前扩展结点 扩展A,先到达B结点 Cr=Cr-w1=14,V=V+v1=45 此时A、B为活结点,B成为当前扩展结点 扩展B,先到达D Cr<w2,D导致一个不可行解,回溯到B 再扩展B到达E E可行,此时A、B、E是活结点,E成为新的扩展结点 扩展E,先到达J Cr<w3,J导致一个不可行解,回溯到E 再次扩展E到达K 由于K是叶结点,即得到一个可行解x=(1,0,0),V=45 K不可扩展,成为死结点,返回到E E没有可扩展结点,成为死结点,返回到B B没有可扩展结点,成为死结点,返回到A A再次成为扩展结点,扩展A到达C Cr=30,V=0,活结点为A、C,C为当前扩展结点 扩展C,先到达F Cr=Cr-w2=15,V=V+v2=25,此时活结点为A、C、F,F成为当前扩展结点 扩展F,先到达L Cr=Cr-w3=0,V=V+v3=50 L是叶结点,且50>45,皆得到一个可行解x=(0,1,1),V=50 L不可扩展,成为死结点,返回到F 再扩展F到达M M是叶结点,且25<50,不是最优解 M不可扩展,成为死结点,返回到F F没有可扩展结点,成为死结点,返回到C 再扩展C到达G Cr=30,V=0,活结点为A、C、G,G为当前扩展结点 扩展G,先到达N,N是叶结点,且25<50,不是最优解,又N不可扩展,返回到G 再扩展G到达O,O是叶结点,且0<50,不是最优解,又O不可扩展,返回到G G没有可扩展结点,成为死结点,返回到C C没有可扩展结点,成为死结点,返回到A A没有可扩展结点,成为死结点,算法结束,最优解X=(0,1,1),最优值V=50
遍历排列树函数
void swap(int *p,int *q) { int temp; temp=*p;*p=*q;*q=temp; } void backtrack(int i) { int j; if(i>n) { for(j=1;j<=n;j++) cout<<s[j]<<" "; cout<<endl; return; } for(j=i;j<=n;j++) { swap(&s[i],&s[j]); backtrack(i+1); if(……) backtrack(i+1); //引入剪枝函数 swap(&s[i],&s[j]); } }
例2 旅行售货员问题
30 问题描述:某售货员要 2 1 到若干城市去推销商品, 10 6 5 4 已知各城市之间的路程, 3 4 20 他要选定一条从驻地出发, A 经过每个城市一遍,最后 1 B 回到住地的路线,使总的 分析思路: 2 4 3 路程最短。 D E C 1、该问题的解空间 2 4 2 3 3 4 该问题有(n-1)!条可选 2、对应的解空间结构 F G H I J K 路线。 4 3 4 2 3 2 最 优 解 (1,3,2,4,1) , L M N O P Q 最优值25。
用回溯法解0-1背包问题
1. void Backtrack(int t){ 1. void Backtrack(int t){ 用一个数组存放目前取得的最优解,再用一个 2. if( t t>n ) ) Output(x) if(Bestp<cp) Bestp = cp; 判断是否是当前最优的, 2. if( >n ; 有两种选择: 数组来产生新的解。如果新解更优,则替代原来 是则替换当前最优值 3. else { 放入或者不 3. else 放入,所以 的最优解,否则就丢弃新解。然后回溯寻找下一 4. if(cw+w[t] <=c) { //进入左子树 x[t] = 0或 1 4. { x[t] = 1 ; 个新解。 5. cw +=w[t]; 5. if (Constraint(t) && Bound(t) ) 6. 为此,我们先回顾一下用回溯法搜索子集树的 cp += p[t]; 6. Backtrack(t+1) ; 7. Backtrack(t + 1) ; 一般递归算法: 7. x[t]= ; 8. cw -=0 w[t]; // 回溯 8. if && Bound(t) ) 9. cp(Constraint(t) -= p[t] ; // 回溯 } 9. Backtrack(t+1) ; //进入右子树 10. if(bound(t+1)>bestp) 11. 10. } backtrack(t+1) ; 12. }} 11. }
2、回溯法的基本思想
回溯法的基本步骤: (1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函 数避免无效搜索。
常用剪枝函数:
用约束函数在扩展结点处剪去不满足约束的子树; 用限界函数剪去得不到最优解的子树。 需要注意的是,问题的解空间树是虚拟的,并不需要在算法 运行时构造一棵真正的树结构,只需要存储从根结点到当前结 点的路径。
3、递归回溯
t表示递归深度。该函数用于对第t层的 回溯法对解空间作深度优先搜索,因此,在一般情况下用 某个结点,搜索其所有子树。 递归方法实现回溯法。 void backtrack (int t) 算法已搜索到一个叶结点, { 输出该结果 if (t>n) output(x); f(n,t),g(n,t)分别表示扩展结点的子树 的起始编号和终止编号。尝试这些选择。 else for (int i=f(n,t);i<=g(n,t);i++) H(i)表示第i个可选值 { x[t]=h(i); if (constraint(t)&&bound(t)) backtrack(t+1); } 分别表示约束函数和限界函数 }
遍历子集树函数
void subset(int i) { if(i>n) { for(int j=1;j<=n;j++) if (x[j]) cout<<s[j]<<" "; cout<<endl; return; } x[i]=1; if(……) subset(i+1); //引入剪枝函数 subset(i+1); x[i]=0; subset(i+1); if(……) subset(i+1); //引入剪枝函数 return; }
0-1背包问题的子程序
0-1 计算右子树的上界的方法是:将剩余物品依其单位重量 背包问题的子程序主要是一个边界函数。在要进入右 int Bound(int i ) { Cleft:剩余重量,b:当前价值 子树时计算上界函数 价值排序,然后依次装入物品,直至装不下时,再装入 bound ,以判断是否可以将右子树剪去。 将剩余物品按 cleft = c-cw ; b = cp ; 引入上界函数后,在到达一个叶节点时不必再检查该叶 该物品的一部分而装满背包。 单位价值顺次 while (i<= n &&w[i]<cleft) { 节点是否优于当前最优解,因为上界函数使算法搜索到的每 由此得到的价值是右子树解的一个上界。 加入,直至满 个叶节点都是迄今为止找到的最优解。 cleft-= w[i]; b+=p[i]; i++;} 如果还有物 if(i <= n) b+=p[i]*cleft/w[i]; 品没有加入, 则用此物品 return b ; 填满背包
5、子集树与排列树
在用回溯法求解问题时,常常遇到两种典型的 解空间树: 子集树:当所给的问题是从 n 个元素的集合 S 中 找出满足某种性质的子集时,相应的解空间树成为 子集树。例:0-1背包问题。 排列树:当所给问题是确定n个元素的满足某种 性质的排列时,相应的解空间树称为排列树。例: 旅行售货员问题。 两者的算法描述请见p143。
4、迭代回溯
采用树的非递归深度优先遍历算法,可将回 溯法表示为一个非递归迭代过程。 下面的例子是用迭代的方法求 {1,2,3,4} 的
所有子集。
int s[]={0,1,2,3,4}; int n=4; int x[5]; void subset2(void) //求{1,2,3,4}的所有子集(非递归) { int t=1; x[t]=0; while(t>0) { while(x[t]<=1) { if(t<=n) if(t==n) { for(int j=1;j<=n;j++) if (x[j]) cout<<s[j]<<" "; cout<<endl; t++; } else { t++;x[t]=0; } else { t--;x[t]++; } } t--;x[t]++; } }
相关文档
最新文档