赞
踩
目录
回溯法的思路主要可以概述为以下2点:
(1)把问题的解空间转化成了图或者树的结构
(2)使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解,并在搜索过程中用剪枝函数避免无效搜索。
深度优先遍历一般只在图和树中涉及,当然,树是图的一种特殊结构。DFS简要来说就是对每一个可能的分支路径深入到不能再深入为止,且不能重复遍历,即每个节点只能遍历一次。
下边举两个例子 说明一下DFS,帮助理解。
如图所示:是一个二叉树,其深度遍历的顺序为:ABD E CF G
遍历路径如图中棕色箭头所示。具体遍历步骤如下:
(1)先从A点出发,既可以选择遍历B也可以选择遍历C,图中选择B;
(2)从B点出发,既可以选择遍历D也可以选择遍历E,图中选择D;
(3)到达D点后,没有可以继续深入的节点,则向上回溯,到达B点,选择B点还没有遍历的其他节点,图中选择E;
(4)到达E点后,没有可以继续深入的节点,则向上回溯到达B,仍没有可以继续深入的节点,向上回溯到A,选择A还没有遍历的其他节点,图中选择C;
(5)到达C点后,既可以选择遍历F也可以选择遍历G,图中选择F;
(6) 到达F点后,没有可以继续深入的节点,则向上回溯到C,选择C点还没有遍历的其他节点,图中选择G;
(7) 达到G点后,没有可以继续深入的节点,则向上回溯到C,仍没有可以继续深入的节点,则向上回溯到A,此时A没有还未遍历的节点,遍历到此结束。
如图所示,是一个无向图。其深度遍历顺序为:ABDFECGH
遍历路径如图中棕色箭头所示。具体遍历步骤如下:
(1)先从A点出发,既可以选择遍历B、C也可以选择遍历H,图中选择B;
(2)从B点出发,既可以选择遍历D也可以选择遍历E,图中选择D;
(3)到达D点后,向下继续深入,图中选择F;
(4) 到达F点后,向下继续深入,图中选择E;
(5)到达E点后,没有可以继续深入的节点,则向上回溯,到达F点,仍没有可以继续深入的节点,向上回溯到D,到达D点,仍没有可以继续深入的节点,向上回溯到B,到达B点,仍没有可以继续深入的节点,向上回溯到A,选择A点还没有遍历的其他节点,图中选择C;
(6)到达C点后,向下继续深入,图中选择G;
(7)到达G点后,没有可以继续深入的节点,则向上回溯到C;
(8) 到达C点后,没有可以继续深入的节点,则向上回溯到A,选择A点还没有遍历的其他节点,图中选择H;
(9) 达到H点后,没有可以继续深入的节点,则向上回溯到A,此时A没有还未遍历的节点,遍历到此结束。
剪枝函数,顾名思义就是,剪去无用的枝条,即在进行遍历时,提前减去不必要的分支,避免无效遍历。其根本目的,是要减少搜索的次数,使程序运行的时间减少,提高算法效率。
应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。
剪枝算法按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝。
可行性剪枝: 该方法判断继续搜索能否得出答案,如果不能直接回溯。
最优性剪枝:它记录当前得到的最优值,如果当前结点已经无法产生比当前更优的解时,可以提前回溯。
有N件物品和一个容量为C的背包。第i件物品的价值是P[i],重量是W[i]。求解将哪些物品装入背包可使价值总和最大。所谓0-1背包,表示每一个物品不能拆解,要么装入,要么不装入。
0-1背包问题是求最优解的问题。在考虑将物品放进背包的原则是:单位价值(P[i]/W[i])越大的,尽量提前放进背包中(即将性价比更高的物品更早装进背包);但是限制条件是,所装物品必须是完整的且重量不能超过背包承重能力。
问题转化:
按照上述思路,先将各物品按照单位价值递减的顺序排序,其次进行判断是否在承重范围值内。
定义:CW(current weight)表示当前重量,CP(current price)表示当前价值,
根节点代表扩展结点 ,其余每一层代表一个物品,越靠近根节点,单位价值越高。选中该物品,即搜索左子树,进行判断。具体执行操作如下所示:
(1)先计算所有物品的单位价值,将其进行降序排列
(2)排列之后,从根节点(扩展节点)出发;
(3)搜索左子树,判断是否满足约束条件(物品装入背包?):
若选中该物品(可行解),CW+=W[i],CP+=P[i],继续向下遍历;
直至遇到不可行解时,开始向上回溯,取出最后一个装入的物品,进入右子树:
(4)进入右子树,首先计算当前节点的上界bound(i),
若bound(i)小于bestp,剪去右子树,继续向上回溯;
否则进行步骤(3);
(5)遇到叶子节点,比较当前价值与bestp,若CP>bestp,则bestp进行更新;
(6)直到遍历完所有的节点(除剪枝部分外)。
已知:p[]={45,25,24}; w[]={16,15,15};背包容量为30,求最优价值?
(1)先将这四件物品的单位价值算出来,按照单位价值降序排列。刚好顺序和原始顺序一致。
(2)开始进行遍历并判断是否装入,
具体步骤如下:(B1,B2,都指的是物品B,C1,C2,C3,C4都指的是物品C ,依此类推 主要是为了区分结点 便于叙述)
首先 物品B1 装入符合条件(最大重量不超过背包容量30的要求),遍历其左子树,此时CW = 16,CP = 45;
到达B1点后,遍历其左子树,发现剩余容量r = 14,C1无法装入背包,剪去B1-C1这条枝;
进入右子树,到达C2后,计算bound(i) = 68.3>45,继续向下遍历。遍历其左子树,发现剩余容量r = 14,D3无法装入背包,剪去C2-D3这条枝;
进入右子树,到达C2后,遇到叶子节点,此时bestp = 45。记录该值。
向上回溯,取出最后一个装入的物品B1,到达A,进入右子树,到达B2;计算bound(i) = 49>45,继续向下遍历;此时CW = 0,CVP= 0;
遍历左子树,到达C3,此时CW = 15,CP= 25;剩余容量r =15。
继续向下遍历 ,到达D5,此时D5可以装入包中,CW = 30,CP = 49。此时CP>bestp = 45,进行更新bestp; bestp = 49。记录bestp和当前包中物品。
由于还有其他结点没有遍历完成,继续进行回溯。
取出最后一个装入的物品D5,到达C3,进入右子树,到达D6,此时CW = 15,CP= 25;到达了叶子节点 判断是否需要更新bestp。由于CP= 25<bestp,不用更新。
继续向上回溯,去除最后一个装入的物品C3,到达B2,向右进行遍历,进入右子树,到达C4,计算bound(i) = 48<bestp,不用向下遍历,进行剪枝。
此时所有结点已经完全遍历,最有价值为49,装入CD即可。
遍历过程如图所示:(图中68.3应该位67.4,bound = 45+14*(24/15)=67.4)
- import java.util.Arrays;
- import java.lang.annotation.ElementType;
-
- /**
- * Author: PuTongFish
- */
- public class Knapsack {
- private static class Element implements Comparable {
- int id;//物品编号
- double d;
-
- private Element(int idd, double dd) {
- id = idd;
- d = dd;
- }
- @Override
- public int compareTo(Object o) {
- double xd = ((Element)o).d;
- if (d < xd) return -1;
- if (d == xd) return 0;
- return 1;
- }
-
- public boolean equals(Object x) {
- return d == ((Element) x).d;
- }
- }
-
- static double c;
- static int n;
- static double[] p;
- static double[] w;
- static double cw;
- static double cp;
- static double bestp;
-
- public static double Knapsack(double[] pp,double[] ww,double cc) {
- c = cc;
- n = pp.length;
- cw = 0.0;
- cp = 0.0;
- bestp = 0.0;
- Element[] q = new Element[n];
-
- for (int i = 1; i <= n; i++) {
- q[i - 1] = new Element(i, pp[i] / ww[i]);
- }
- //排序
- //MergeSort.mergeSort(q);
- for (int i = 1; i <= n; i++) {
- for (int j = 1; j <= n - i; j++) {
- if (q[j - 1].d < q[j].d) {
- Element temp = q[j - 1];
- q[j - 1] = q[j];
- q[j] = temp;
- }
- }
- }
- p = new double[n + 1];
- w = new double[n + 1];
- for (int i = 1; i <= n; i++) {
- p[i] = pp[q[n - i].id];
- w[i] = ww[q[n - i].id];
- }
- Backtrack(1);
- return bestp;
- }
- private static void Backtrack(int i){
- if (i > n) {
- //到达叶结点
- bestp = cp;
- return;
- }
-
- if (cw + w[i] <= c) { //进入左子树
- cw += w[i];
- cp += p[i];
- Backtrack(i + 1);
- cw -= w[i];
- cp -= p[i];
- }
- // float u=Bound(i + 1);
- // float bb=bestp;
- //当前的界是否大于背包当前的值
- if (Bound(i + 1) > bestp) { //进入右子树
- Backtrack(i + 1);
- }
- }
- private static double Bound(int i){
- //计算上界
- double cleft = c - cw; //剩余容量
- double bound = cp;
- //以物品单位重量价值递减序装入物品
- while (i <= n && w[i] <= cleft) {
- cleft -= w[i];
- bound += p[i];
- i++;
- }
- //装满背包
- if (i <= n){
- double aa = p[i] * cleft;
- double bb = w[i];
- double temp = aa / bb;
- //注意:如果这样写:float temp=p[i] * cleft/w[i];
- //则temp计算出来是整数,因为右边是先按整数来算,再将int转float;
- bound += temp;
- // System.out.println(i);
- //System.out.println("界");
- //System.out.println(bound);
- }
- return bound;
- }
-
-
- public static void main(String[] args) {
- double p[]={45,25,24};
- double w[]={16,15,15};
- double c = Knapsack(p,w,30);
- System.out.println("0-1背包问题的最优值为:"+c);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。