0-1揹包問題我們已經說過很多次了,這次是用最近學的分支限界法解決。分支限界法就是利用佇列或者優先佇列在儲存解空間樹的活結點,並每次彈出乙個作為擴充套件結點,是一種廣度優先遍歷,區別於回溯法的深度優先遍歷。而優先佇列時間複雜度更低,因為我們每次加入乙個活結點時,佇列都會排序,所以我們出隊的結點一定是優先順序最高的。下面我們就來說一下如何利用分支限界法解決0-1揹包問題。
在回溯法中我們談到了上界函式這個概念,在分支限界法中也有這個函式,這個函式主要是來約束右節點的。在回溯法中我們的上界函式是:剩餘價值+當前價值》當前最優價值
,當這個函式滿足時我們才有必要將右子節點加入到佇列中。但是分支限界法這個上界函式有一些變化,具體什麼變化可以往下看。
在使用分支限界的時候我們會首先考慮優先佇列,因為優先佇列往往能降低複雜度讓演算法更快。那麼我們就要定義優先順序。在我們談論用分支限界法解決裝載問題的時候我們也設定了優先順序,當時我們的優先順序設為當前重量+剩餘重量
。但是在這裡是0-1揹包問題,並不是完全揹包問題,即物品只能裝或者不裝,這裡我們定義上界函式的時候,假設揹包可以裝一部分,即當揹包裝不下一整件物品時我們只裝一部分。所以我們定義上界函式為當前重量+剩餘物品中單位重量價值最大的物品的平均價值*剩餘重量
,因此在這裡我們可以想到,我們需要按照物體單位重量價值從大到小的順序排列物品。
於是我們就可以推演出上界函式,如果當前重量+剩餘物品中單位重量價值最大的物品的平均價值*剩餘重量 > 當前最優價值
時說明我們有必要將右節點加入到佇列中,否則不加入右節點。
慢慢地我們更新當前最優價值,當前價值,當前揹包容量,取下一活結點作為擴充套件結點,繼續進行遍歷,直到我們到達了第乙個葉結點,那就表明我們已經找到了最優解(因為優先佇列每次都會彈出優先順序最高的活結點)。
最後乙個問題,我們怎樣獲得最優解路徑呢?我們在寫**的時候會寫乙個bbnode類作為子集樹中的結點類,它的成員有parent父節點和bool型別的lchild,指示左節點是否加入活結點。lchild就是我們記錄最優路徑的主要標量,如果lchild=1說明此時我們走了左節點,lchild=0說明此時我們走了右節點。
但是這不是回溯法,而是分支限界法,我們的資料結構不是什麼子集樹,而是乙個優先佇列,怎麼把子集樹的結點和優先佇列中的元素關聯起來呢?答案是我們只需要建立乙個最大堆,堆結點類中的資料成員包括堆結點當前重量、當前價值、最優價值、還有指向子集樹結點的指標以及活結點在樹中的層數。
最後我們直接返回最優解和最優解路徑。下面我們來看**裡的其他細節,我都寫好了注釋。
#include
using
namespace std;
//揹包物品類
class
object
private
:int id;
float d;
//單位重量價值};
//子集樹中的結點類
class
bbnode
;//堆中堆結點類
class
heapnode
private
:int uprofit;
//結點的價值上界
int profit;
//結點相應的價值
int weight;
//結點相應的重量
int level;
//活結點在子集樹中所處的層序號
bbnode *ptr;
//指向活結點在子集樹中相應結點的指標};
//揹包類,記錄當前揹包的最大價值,當前價值以及容量
class
knap
;int knap<
int,
int>
::bound
(int i)
//計算結點所相應的價值上界,貪心思想
if(ib +
= p[i]
/ w[i]
* cleft;
return b;
}//將活結點加入優先佇列中
void knap<
int,
int>
::addlivenode
(int up,
int cp,
int cw,
bool ch,
int lev)
//按照優先順序遍歷子集樹,將活結點加入到優先佇列中
int knap<
int,
int>
::maxknapsack()
addlivenode
(up,cp+p[i]
,cw+w[i]
,true
,i+1);
}//這裡就算的bound只是為了給右節點乙個約束,看是否有必要將右節點加入到活結點佇列中
up =
bound
(i+1);
if(up>=bestp)
//取下一擴充套件結點
heapnode<
int,
int> n;
h->
delmax
(n);
up = n.uprofit;
//更新最大價值
cp = n.profit;
//更新當前價值,為新的擴充套件結點的價值
cw = n.weight;
//更新當前重量,為新的擴充套件結點的重量
e = n.ptr;
//下一擴充套件點,子集樹中的點
i = n.lev;
}for
(int j = n;j >
0;j--)}
//knapsack函式完成對輸入資料的預處理,我們輸入的object類的物品,根據我們的最大上界函式即bound函式可知
//我們需要將單位重量的**從大到小排序,從而計算bound,將其作為優先順序
//所以這個函式的作用是將輸入的object類物件排序傳遞給knap類物件,返回最大價值
intknapsack
(int p,
int w,
int c,
int n,
int bestx)
if(w <= c)
sort
(q,n)
;//這裡自定義乙個函式依單位重量價值排序
//建立類knap的資料成員
knap<
int,
int> k;
k.p =
newint
[n+1];
k.w =
newint
[n+1];
for(
int i =
1;i <= n;i++
) k.cp =0;
k.cw =0;
k.c = c;
k.n = n;
int bestp = k.
maxknapsack()
;//呼叫函式求問題的最優解
for(
int j =
1;j <= n;j++
)delete
q;delete
k.w;
delete
k.p;
delete
k.bestx;
return bestp;
}
分支限界法確實是比回溯法更簡單,因為我們廣度優先遍歷,而且每次找優先順序最高的,當達到葉結點時一定是最優的。演算法的時間複雜度主要是在計算最優解的過程,這取決於我們達到葉結點的時候經過了多少節點,有可能我們沒有遍歷完一層,下一層的優先順序更高,我們就會更進一層,所以取決於我們中間經歷過的結點。樹的結點數目為2^n.
但是最後一層我們只會找到乙個結點,而最後一層的結點數目應該是2^(n-1).
所以我們的時間複雜度最差應為2^(n-1)(2^n-2^(n-1)
),但基本不可能。
0 1揹包問題 分支限界法
0 1揹包問題可描述為 n個物體和乙個揹包。對物體i,其價值為value,重量為weight,揹包的容量為w 如何選取物品裝入揹包,使揹包中所裝入的物品總價值最大?2.1 用到的資料結構 class goods 定義貨物資料型別 class knapsack 揹包 2.2 演算法步驟1 定 空間。x...
分支限界法 0 1揹包問題
分支限界法類似於回溯法,也是在問題的解空間上搜尋問題解的演算法。一般情況下,分支限界法與回溯法的求解目標不同。回溯法的求解目標是找出解空間中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的乙個解,或是在滿足約束條件的解中找出使某一目標函式值達到極大或極小的解,即在某種意義下的最優解...
0 1揹包問題(分支限界法)
需求分析 0.問題描述 給定一揹包的容量c,和n個物品的重量wi價值vi,求在揹包容量允許的條件下能裝入的最大價值 1.問題分析 解空間 子集樹,第i層每個節點有兩棵子樹 選擇物品i,不選擇物品i 優先佇列的優先順序如何確定?每個節點的祖先節點都已經確定,那麼可以得到已經裝入揹包的物品的價值,加上剩...