分支限界法和以前講的回溯法有一點類似,二者都是在問題的解的空間上搜索問題的解。可是二者仍是有一些區別的,回溯法是求解在解的空間中的知足的全部解,分支限界法則是求解一個最大解或最小解。這樣,二者在解這一方面仍是有一些不一樣的。以前回溯法講了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; } }
上面就是代碼,下面說一說具體的細節,隊列中添加結點的方式是先進先出,這裏將左節點放到前面就是將優先的結點先出,其中每一個判斷的判斷條件都是要注意的點,還有就是結點的索引要注意和物品數組中下標相差一,這個要注意。要否則構建最優解的時候會越界,其餘的一些細節都寫在註釋了裏。再也不累述。遞歸
下面講一講本身的一些想法,對於本身一開始想用數組實現的想法,我倒如今仍是認爲是對的,由於看到如今,算法是實現了,可是估計效率不是很高,我估計沒有數組實現的方式效率高,這裏由於能力有限,只能放棄數組的實現方式。也是算法下一步進步的方式吧。
結束。