分支限界法解決01揹包問題

  分支限界法和以前講的回溯法有一點類似,二者都是在問題的解的空間上搜索問題的解。可是二者仍是有一些區別的,回溯法是求解在解的空間中的知足的全部解,分支限界法則是求解一個最大解或最小解。這樣,二者在解這一方面仍是有一些不一樣的。以前回溯法講了N後問題,這個問題也是對於這有多個解,可是今天講的01揹包問題是隻有一個解的。下面就講講分支限界法的基本思想。java

  分支限界法常以廣度優先或以最小消耗(最大效益)優先的方式搜索問題的解空間樹。問題的解空間樹是表示問題解空間的一顆有序樹,常見的有子集樹和排列樹。分支限界法和回溯法的區別還有一點,它們對於當前擴展結點所採用的擴展方式也是不相同的。分支限界法中,對於每個活結點只有一次機會成爲擴展結點。活結點一旦成爲了擴展結點,就一次性產生其全部的子結點,子結點中,不符合要求的和非最優解的子結點將會被捨棄,剩下的子結點將加入到活結點表中。再重複上面的過程,直到沒有活結點表中沒有結點,至此完成解決問題的目的。node

  分支限界法大體的思想就是上面的敘述,如今就能夠發現,對於結點的擴展將會成爲分支限界法的主要核心。因此,分支限界法常見的有兩種擴展結點的方式,1.隊列式(FIFO)分支限界法,2.優先隊列式分支限界法。兩種方法的區別就是對於活結點表中的取出結點的方式不一樣,第一種方法是先進先出的方式,第二種是按優先級取出結點的方式。兩中方法的區別下面也會提到。算法

  在揹包問題中還會提到一個子樹上界的概念,其實就是回溯法中的剪枝函數,只不過,分支限界法裏的剪枝函數改進了一些,剪枝函數一樣也是分支限界法裏比較重要的東西。數組

  下面就講一講01揹包問題的實現。01揹包問題和前面講的揹包問題的區別不大,就是01揹包問題的物品不能夠只放入部分,01揹包問題的物品只能放入和不放入兩個選擇,這也是名字中01的緣由。其餘的和揹包問題相差不大,這裏也再也不累述。函數

算法的主體是比較容易想的,首先,將數據進行處理,這也是上面講到的第二種取結點的方式(優先隊列式)。由於要給每一個物品設置優先級,這裏將價值做爲優先級顯然不夠好,就想到了將價值與重量的比值(權重值)做爲優先級。要寫一個排序算法,將物品數組中物品按權重值排序。下面就要想一會兒樹上界函數了,這裏上界的函數借鑑了一下揹包問題的結局方案,由於子樹的最大值必定小於非01揹包問題的最優解,因此用到了以前揹包問題的代碼,將代碼進行了處理,須要傳入參數,也要有返回值,這裏就再也不重複敘述了。this

  下面就是代碼的主體,這一部分我想了大概兩個星期,一開始的思路出現了問題,老是想着使用數組來實現算法的主體,同時在使用遞歸來循環遍歷數組,後來發現,這樣作就和回溯法如出一轍,後來借鑑了一下網上的代碼,將本身的主體代碼改了改,多寫了一個類(結點類),剩下的就比較好實現了,將初始結點添加到結點表中,找到左節點和右節點,同時利用函數判斷結點是否符合要求,在將左節點和右節點添加到結點表中,在循環遍歷結點表,知道結點表中沒有活結點,一直重複這個步驟。在左節點的判斷中,同時還要判斷左節點的值是否是大於最優解,算法的大部分都是在判斷。算法主體就結束了。spa

  再來說一講算法的構建最優解,這個我也是想不出來,老師最後提醒了我,先是將結點添加到另外一個節點表中,在問題的結束後,遍歷找到最優解的父節點,判斷父節點是否是左節點,在重複這個步驟,直到沒有父節點。完成後將左節點的索引標記爲放入揹包中,這樣就完成了最優解的構建。剩下的問題就是一些細節問題了。blog

  代碼以下:排序

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

  上面就是代碼,下面說一說具體的細節,隊列中添加結點的方式是先進先出,這裏將左節點放到前面就是將優先的結點先出,其中每一個判斷的判斷條件都是要注意的點,還有就是結點的索引要注意和物品數組中下標相差一,這個要注意。要否則構建最優解的時候會越界,其餘的一些細節都寫在註釋了裏。再也不累述。遞歸

  下面講一講本身的一些想法,對於本身一開始想用數組實現的想法,我倒如今仍是認爲是對的,由於看到如今,算法是實現了,可是估計效率不是很高,我估計沒有數組實現的方式效率高,這裏由於能力有限,只能放棄數組的實現方式。也是算法下一步進步的方式吧。

  結束。

相關文章
相關標籤/搜索