回溯法-------0-1背包问题(DFS、剪枝函数)

目录

一  回溯法概述

二 深度优先搜索策略(DFS)

1 树中的深度优先遍历

2 图中的深度优先遍历

三 剪枝函数

四 0-1背包问题

1 问题描述:

2 问题求解:

3 举例说明:

4 算法实现

5 结果展示:

五 复杂度分析


一  回溯法概述

回溯法的思路主要可以概述为以下2点:

(1)问题的解空间转化成了或者的结构

(2)使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行或者最优解,并在搜索过程中用剪枝函数避免无效搜索。

二 深度优先搜索策略(DFS)

深度优先遍历一般只在图和树中涉及,当然,树是图的一种特殊结构。DFS简要来说就是对每一个可能的分支路径深入到不能再深入为止,且不能重复遍历,即每个节点只能遍历一次

下边举两个例子 说明一下DFS,帮助理解。

1 树中的深度优先遍历

如图所示:是一个二叉树,其深度遍历的顺序为: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没有还未遍历的节点,遍历到此结束。

2 图中的深度优先遍历

如图所示,是一个无向图。其深度遍历顺序为: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没有还未遍历的节点,遍历到此结束。

三 剪枝函数

剪枝函数,顾名思义就是,剪去无用的枝条,即在进行遍历时,提前减去不必要的分支,避免无效遍历。其根本目的,是要减少搜索的次数,使程序运行的时间减少,提高算法效率。

应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。

剪枝算法按照其判断思路可大致分成两类:可行性剪枝及最优性剪枝。

可行性剪枝: 该方法判断继续搜索能否得出答案,如果不能直接回溯。

最优性剪枝:它记录当前得到的最优值,如果当前结点已经无法产生比当前更优的解时,可以提前回溯。

四 0-1背包问题

1 问题描述:

有N件物品和一个容量为C的背包。第i件物品的价值是P[i],重量是W[i]。求解将哪些物品装入背包可使价值总和最大。所谓0-1背包,表示每一个物品不能拆解,要么装入,要么不装入。

2 问题求解:

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)遇到叶子节点,比较当前价值与bestpCP>bestp,则bestp进行更新;

6)直到遍历完所有的节点(除剪枝部分外)。

3 举例说明:

已知: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)

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);
    }
}

5 结果展示:

五 复杂度分析

  • 38
    点赞
  • 275
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值