• 分支限界法解决01背包问题


      分支限界法和之前讲的回溯法有一点相似,两者都是在问题的解的空间上搜索问题的解。但是两者还是有一些区别的,回溯法是求解在解的空间中的满足的所有解,分支限界法则是求解一个最大解或最小解。这样,两者在解这一方面还是有一些不同的。之前回溯法讲了N后问题,这个问题也是对于这有多个解,但是今天讲的01背包问题是只有一个解的。下面就讲讲分支限界法的基本思想。

      分支限界法常以广度优先或以最小消耗(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一颗有序树,常见的有子集树和排列树。分支限界法和回溯法的区别还有一点,它们对于当前扩展结点所采用的扩展方式也是不相同的。分支限界法中,对于每一个活结点只有一次机会成为扩展结点。活结点一旦成为了扩展结点,就一次性产生其所有的子结点,子结点中,不符合要求的和非最优解的子结点将会被舍弃,剩下的子结点将加入到活结点表中。再重复上面的过程,直到没有活结点表中没有结点,至此完成解决问题的目的。

      分支限界法大致的思想就是上面的叙述,现在就可以发现,对于结点的扩展将会成为分支限界法的主要核心。所以,分支限界法常见的有两种扩展结点的方式,1.队列式(FIFO)分支限界法,2.优先队列式分支限界法。两种方法的区别就是对于活结点表中的取出结点的方式不同,第一种方法是先进先出的方式,第二种是按优先级取出结点的方式。两中方法的区别下面也会提到。

      在背包问题中还会提到一个子树上界的概念,其实就是回溯法中的剪枝函数,只不过,分支限界法里的剪枝函数改进了一些,剪枝函数同样也是分支限界法里比较重要的东西。

      下面就讲一讲01背包问题的实现。01背包问题和前面讲的背包问题的区别不大,就是01背包问题的物品不可以只放入部分,01背包问题的物品只能放入和不放入两个选择,这也是名字中01的原因。其他的和背包问题相差不大,这里也不再累述。

    算法的主体是比较容易想的,首先,将数据进行处理,这也是上面讲到的第二种取结点的方式(优先队列式)。因为要给每个物品设置优先级,这里将价值作为优先级显然不够好,就想到了将价值与重量的比值(权重值)作为优先级。要写一个排序算法,将物品数组中物品按权重值排序。下面就要想一下子树上界函数了,这里上界的函数借鉴了一下背包问题的结局方案,因为子树的最大值一定小于非01背包问题的最优解,所以用到了之前背包问题的代码,将代码进行了处理,需要传入参数,也要有返回值,这里就不再重复叙述了。

      下面就是代码的主体,这一部分我想了大概两个星期,一开始的思路出现了问题,总是想着使用数组来实现算法的主体,同时在使用递归来循环遍历数组,后来发现,这样做就和回溯法一模一样,后来借鉴了一下网上的代码,将自己的主体代码改了改,多写了一个类(结点类),剩下的就比较好实现了,将初始结点添加到结点表中,找到左节点和右节点,同时利用函数判断结点是否符合要求,在将左节点和右节点添加到结点表中,在循环遍历结点表,知道结点表中没有活结点,一直重复这个步骤。在左节点的判断中,同时还要判断左节点的值是不是大于最优解,算法的大部分都是在判断。算法主体就结束了。

      再来讲一讲算法的构建最优解,这个我也是想不出来,老师最后提醒了我,先是将结点添加到另一个节点表中,在问题的结束后,遍历找到最优解的父节点,判断父节点是不是左节点,在重复这个步骤,直到没有父节点。完成后将左节点的索引标记为放入背包中,这样就完成了最优解的构建。剩下的问题就是一些细节问题了。

      代码如下:

    package sf;
    
    import java.util.LinkedList;
    import java.util.Scanner;
    
    /*
     * 01背包问题,分支限界法
     */
    public class demo8 {
    	/*
    	 * 主方法
    	 */
    	public static void main(String[] args) {
    		//输入数据
    		System.out.println("请输入背包的容量w和物品的个数n");
    		Scanner reader = new Scanner(System.in);
    		int w = reader.nextInt();// 背包的容量
    		int n = reader.nextInt();// 物品的个数
    		int solution=-1;
    		BLBag[] p = new BLBag[n];
    		System.out.println("请依次输入各个物品的重量w和价值v和名称s");
    		int weigth;
    		int value;
    		String pid;
    		for (int i = 0; i < n; i++) {
    			pid = reader.next();
    			weigth = reader.nextInt();
    			value = reader.nextInt();
    			p[i] = new BLBag(pid, weigth, value);
    		}
    		// 输入数据结束
    		/*
    		 * 数据
    		 * 001 16 45 002 15 25 003 15 25
    		 */
    		// 算法开始
    		//声明状态数组并初始化为空
    		Integer[] a=new Integer[n];
    		for(int i=0;i<n;i++) a[i]=null;
    		//对p数组按权重排序
    		sort(p);
    		//打印结果
    		int haha=branchandlimit(p, w,  a, solution);
    		System.out.println("最优解为:"+haha);
    	}
    	/*
    	 * 权重排序,选择排序
    	 */
    	public static void sort(BLBag[] p) {
    		BLBag t;
    		for (int i = 0; i < p.length; i++) {
    			int max = i;
    			t = p[i];
    			for (int j = i; j < p.length; j++) {
    				if (t.wi < p[j].wi) {
    					t = p[j];
    					max = j;
    				}
    			}
    			t = p[i];
    			p[i] = p[max];
    			p[max] = t;
    
    		}
    	}
    	/*
    	 * 求上界的函数   数组p 当前位置  当前背包重量    返回是最大价值(不包含背包的已有价值)
    	 */
    	public static double findbound(BLBag[] p,int i,int weight)
    	{
    		double value = 0;
    		//将状态位后面的物品求贪心算法的解,上界函数的解为返回值+当前背包价值
    		forLOOP:for(int k=i;k<p.length;k++)//循环名字
    		{
    			//贪心算法求解问题(修改版)
    			if(p[k].weight<weight){
    				value=value+p[k].value;
    				weight=weight-p[k].weight;
    			}else{
    				double a=weight*p[k].wi;//当前价值				
    				value=value+a;
    				weight=0;
    				break forLOOP;//跳出循环
    			}
    		}
    		return value;
    		
    	}
    	/*
    	 * 分支限界法主体 参数分别为物品数组p,重量,价值,状态数组,当前考虑位置i ,最优解
    	 */
    	public static int branchandlimit(BLBag[] p,int weight,Integer[] a,double solution)
    	{	
    		//声明队列
    		LinkedList<Node> nodelist=new LinkedList<Node>();
    		LinkedList<Node> nodesolution=new LinkedList<Node>();
    		nodelist.add(new Node(0, 0, 0));
    		nodesolution.add(new Node(0,0,0));
    		while(!nodelist.isEmpty())
    		{
    			//取出元素
    			Node node = nodelist.pop();
    			//判断条件,节点的不放入的最大值大于当前最优解,节点小于数组的长度
    			//这里不用等于,必须要大于
    			if(node.getUnbounvalue()+node.getCurrvalue()>solution && node.getIndex()<p.length)
    			{				
    				//左节点
    				int leftWeight=node.getCurrweight()+p[node.getIndex()].weight;
    				int leftvalue=node.getCurrvalue()+p[node.getIndex()].value;				
    				Node left=new Node(leftWeight, leftvalue, node.getIndex()+1);
    				//设置左节点的父节点
    				left.setFather(node);
    				left.setIsleft(true);
    				//将左节点添加到最优解队列中
    				nodesolution.add(left);
    				//设置左节点的上界价值
    				left.setUnbounvalue((int)findbound(p, node.getIndex(), weight-node.getCurrweight()));
    				//左节点的重量小于等于背包的承重,且左节点的上界价值大于最优解
    				if(left.getCurrweight()<=weight && left.getUnbounvalue()+left.getCurrvalue()>solution)
    				{				
    					//将节点加入队列中
    					nodelist.add(left);
    					a[node.getIndex()]=1;
    					//将最优值重新赋值  条件就是节点的当前价值大于问题的最优解
    					if(left.getCurrvalue()>solution)
    					{
    						solution=left.getCurrvalue();
    						//System.out.println("放入的物品有:"+p[node.getIndex()].pid);
    					}
    				}
    				//右节点   右节点的设置不需要太多,和父节点差不多
    				Node right=new Node(node.getCurrweight(), node.getCurrvalue(), node.getIndex()+1);
    				//将右节点添加到最优解队列中
    				right.setFather(node);
    				right.setIsleft(false);
    				nodesolution.add(right);
    				right.setUnbounvalue((int)findbound(p,node.getIndex(),weight-node.getCurrweight()));
    				//右节点的上界价值大于当前最优解
    				if(right.getUnbounvalue()+node.getCurrvalue()>solution)
    				{
    					//添加右节点
    					nodelist.add(right);
    					a[node.getIndex()]=0;
    				}
    			}
    		}
    		
    		/*
    		 * 调用最优解方法
    		 */
    		pr(nodesolution,(int)solution,p);
    		//返回最优解
    		
    		return (int) solution;
    	}
    	/**
    	 * 
    	 * @Description: 求解最优解的方法
    	 * @param @param nodesolution   
    	 * @return void  
    	 * @throws
    	 * @author yanyu
    	 * @date 2018年5月21日
    	 */
    	//参数为
    	public static void pr(LinkedList<Node> nodesolution,int solution,BLBag[] p)
    	{
    		int[] a=new int[p.length];
    		Node prnode=null;
    		//从list中循环遍历最优解的节点
    		for(Node node:nodesolution)
    		{
    			if(node.getCurrvalue()==solution){
    				//System.out.println("最优解的父节点的索引为:"+node.getFather().getIndex());
    				prnode=node;
    			}
    		}
    		//循环遍历最优节点的父节点,判断其是否为左节点
    		while (prnode.getFather()!=null) 
    		{
    			if(prnode.isIsleft())
    			{
    				a[prnode.getIndex()-1]=1;
    			}
    			prnode=prnode.getFather();
    		}
    		//打印
    		for(int i=0;i<p.length;i++) 
    		{
    			if(a[i]==1) System.out.println("放入了物品:"+p[i].pid);
    		}
    	}
    	
    	
    }
    /*
     * 背包类
     */
    class BLBag {
    	public int weight;// 重量
    	public int value;// 价值
    	public double wi;// 权重
    	public String pid;// 背包名称
    	public BLBag(String pid, int weight, int value) {
    		this.weight = weight;
    		this.value = value;
    		this.pid = pid;
    		this.wi = (double) value / weight;
    	}
    }
    /**
     * 
     * ClassName: Node 
     * @Description: 节点类
     * @author yanyu
     * @date 2018年5月17日
     */
    class Node
    {
    	//当前物品的属性
    	private int currweight;//当前重量
    	private int currvalue;//当前价值
    	private int unbounvalue;//上界价值
    	private int index;//索引
    	private Node father;//父节点
    	private boolean isleft;//是否为左节点
    	public boolean isIsleft() {
    		return isleft;
    	}
    	public void setIsleft(boolean isleft) {
    		this.isleft = isleft;
    	}
    	public Node getFather() {
    		return father;
    	}
    	public void setFather(Node father) {
    		this.father = father;
    	}
    	public int getCurrweight() {
    		return currweight;
    	}
    	public void setCurrweight(int currweight) {
    		this.currweight = currweight;
    	}
    	public int getCurrvalue() {
    		return currvalue;
    	}
    	public void setCurrvalue(int currvalue) {
    		this.currvalue = currvalue;
    	}
    	public int getUnbounvalue() {
    		return unbounvalue;
    	}
    	public void setUnbounvalue(int unbounvalue) {
    		this.unbounvalue = unbounvalue;
    	}
    	public int getIndex() {
    		return index;
    	}
    	public void setIndex(int index) {
    		this.index = index;
    	}
    	//构造函数
    	public Node(int currweight,int currvalue,int index)
    	{
    		this.currweight=currweight;
    		this.currvalue=currvalue;
    		this.index=index;
    	}
    	
    }
    

      上面就是代码,下面说一说具体的细节,队列中添加结点的方式是先进先出,这里将左节点放到前面就是将优先的结点先出,其中每个判断的判断条件都是要注意的点,还有就是结点的索引要注意和物品数组中下标相差一,这个要注意。要不然构建最优解的时候会越界,其他的一些细节都写在注释了里。不再累述。

      下面讲一讲自己的一些想法,对于自己一开始想用数组实现的想法,我倒现在还是认为是对的,因为看到现在,算法是实现了,但是估计效率不是很高,我估计没有数组实现的方式效率高,这里因为能力有限,只能放弃数组的实现方式。也是算法下一步进步的方式吧。

      结束。

     

  • 相关阅读:
    Filecoin:一种去中心化的存储网络(二)
    Filecoin:一种去中心化的存储网络(一)
    HTTP
    数据结构中的查找
    剑指offer-高质量的代码
    C++中STL容器的比较
    PBFT算法的相关问题
    springmvc最全约束
    springmvc入门(一)
    spring入门(一)
  • 原文地址:https://www.cnblogs.com/yanyu01/p/9075704.html
Copyright © 2020-2023  润新知