分支限界法類似於回溯法,也是在問題的解空間上搜尋問題解的演算法。一般情況下,分支限界法與回溯法的求解目標不同。回溯法的求解目標是找出解空間中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的乙個解,或是在滿足約束條件的解中找出使某一目標函式值達到極大或極小的解,即在某種意義下的最優解。
由於求解目標不同,導致分支限界法與回溯法對解空間的搜尋也不相同。回溯法以深度優先的方式搜尋解空間,而分支限界法則以廣度優先或以最小耗費優先的方式搜尋解空間。
分支限界法的搜尋策略是:在擴充套件結點處,先生成其所有的兒子結點(分支),然後再從當前的活結點表中選擇下乙個擴充套件結點。為了有效地選擇下一擴充套件結點,加速搜尋的程序,在每一活結點處,計算乙個函式值(限界),並根據函式值,從前活結點表中選擇乙個最有利的結點作為擴充套件結點,使搜尋朝著解空間上有最優解的分支推進,以便盡快得找出乙個最優解。這種方法稱為分支限界法。人們已經用分支限界法解決了大量離散最優化的問題。
分支限界法即基本思想:
分支限界法常以廣度優先或以最小耗費(最大效益)優先的方式搜尋問題的解空間樹。問題的解空間樹是表示問題解空間的一顆有序樹,常見的有子集樹和排列樹。在搜尋問題的解空間樹時,分支限界法與回溯法的主要區別在於它們對當前擴充套件結點所採用的擴充套件方式不同。在分支限界法中,每乙個活結點只有一次機會成為擴充套件結點。活結點一旦成為擴充套件結點,就一次性產生其所有兒子結點。在這些兒子結點中,導致不可行解或導致非最優解的兒子結點被捨棄,其餘兒子結點被加入活結點表中,此後,從活結點表中取下一結點成為當前擴充套件結點,並重複上述結點擴充套件過程。這個過程一直持續到找到所需的解或活結點表為空時為止。
從活結點表中選擇下一擴充套件結點的不同方式導致不同的分支限界法。最常見的有一下兩種方式。
(1)佇列式(fifo)分支限界法
佇列式分支限界法將活結點表組織成乙個佇列,並按佇列的先進先出原則選擇下乙個結點為當前擴充套件結點。
(2)優先佇列式分支限界法
優先佇列式的分支限界法將活結點表組織成乙個優先佇列,並按優先佇列中規定的結點優先順序選擇優先順序最高的下乙個結點成為當前擴充套件結點。優先佇列中規定的結點優先順序常用乙個與該結點相關的數值p來表示。結點優先順序的高低與p值的大小相關。
看一下熟悉的0-1揹包問題怎麼用分支限界法來解決。
輸入:物品的數目n,揹包的容量c。各個物品的重量wi,各個物品的價值vi。
輸出:裝入揹包的最大價值、物品i是否裝入揹包,是的話輸出1,反之輸出0
執行結果:
在解0-1揹包問題的優先佇列式分支限界法中,活結點優先佇列中結點元素n的優先順序由該結點的上界函式bound計算處的值uprofit給出。該上界函式在解0-1揹包問題的回溯法中討論過,子集樹中以結點n為根的子樹中任一結點的價值不超過n.profit,可用乙個最大堆來實現活結點優先佇列。堆中元素型別為heapnode,其私有成員有uprofit,profit,weight和level。對於任意活結點n,n.weight是結點n所相應的重量;n.profit是n所相應的價值;n.uprofit是結點n的價值上界,最大堆以這個值作為優先順序。子集空間樹中結點型別為bbnode。
//物品
class object
private:
int id; //物品標號
float d; //單位重量價值
};//類的宣告
template class knap;
class bbnode
;//堆結點
template class heapnode
private:
typew weight; //結點所對應的重量
typep profit, //結點所對應的價值
uprofit; //結點的價值上界
bbnode *ptr; //指向活結點在子集樹中相應結點的指標
int level; //活結點在子集樹中所處的層序號
};
演算法中用到的類knap與解0-1揹包問題的回溯法中用到的類kanp十分相似。它們的區別是新的類中沒有成員變數bestp,而增加了新的成員bestx。bestx[i]=1當且僅當最優解含有物品i。
template class knap
;
上界函式bound計算結點所相應價值的上界。
template typep knap::bound(int i)
//裝填剩餘容量裝滿揹包
if(i <= n)
b += 1.0*p[i]/w[i] * cleft;
return b;
}
函式addlivenode將乙個新的活結點插入到子集樹和優先佇列中。
template void knap::addlivenode(typep up, typep cp, typew cw, bool ch, int lev)
演算法maxkanpsack實施對子集樹的優先佇列式分支限界搜尋。其中假定各物品依其單位重量價值從大到小排序。相應的排序過程可在演算法的預處理部分完成。
演算法中e是當前擴充套件結點;cw是該結點所相應的重量;cp是相應的價值;up是價值上界。演算法的while迴圈不斷擴充套件結點,直到子集樹的葉結點稱為擴充套件結點時為止。此時優先佇列中所有活結點的價值上界均不超過該葉結點的價值。因此該葉結點相應的解為問題的最優值。
在while迴圈內部,演算法首先檢查當前擴充套件結點的左兒子結點的可行性。如果該左兒子結點是可行結點,則將它加入到子集樹和活結點優先佇列中。當前擴充套件結點的右兒子結點一定是可行結點,僅當右兒子結點滿足上界約束時才將它加入子集樹和活結點優先佇列。
template typep knap::maxknapsack()
//檢查當前擴充套件結點的右兒子
up = bound(i+1);
if(up >= bestp) //右兒子可能含最優解
addlivenode(up, cp, cw, false, i+1);
//取下一擴充套件結點
n = pq.top();
pq.pop();
e = n.ptr;
cw = n.weight;
cp = n.profit;
up = n.uprofit;
i = n.level;
}//構造當前最優解
for(i = n; i > 0; i--)
return cp;
}
下面的knapsack完成對輸入資料的預處理。其主要任務是將各物品依其單位重量價值從大到小排好序。然後呼叫maxknapsack完成對子集樹的優先佇列式分支限界搜尋。
template typep knapsack(typep *p, typew *w, typew c, int n, int *bestx)
if(w <= c)
return p; //所有物品裝包
sort(q, q+n); //依單位重量價值排序
knapk; //建立類knap的資料成員
k.p = new typep[n+1];
k.w = new typew[n+1];
for(i = 1; i <= n; i++) //初始化
k.n = n;
k.c = c;
k.cp = 0;
k.cw = 0;
bestp = k.maxknapsack(); //呼叫maxknapsack求問題的最優解
for(i = 1; i <= n; i++)
bestx[q[i-1].id] = k.bestx[i];
delete q;
delete k.p;
delete k.w;
delete k.bestx;
return bestp;
}
0 1揹包問題 分支限界法
0 1揹包問題可描述為 n個物體和乙個揹包。對物體i,其價值為value,重量為weight,揹包的容量為w 如何選取物品裝入揹包,使揹包中所裝入的物品總價值最大?2.1 用到的資料結構 class goods 定義貨物資料型別 class knapsack 揹包 2.2 演算法步驟1 定 空間。x...
0 1揹包問題(分支限界法)
需求分析 0.問題描述 給定一揹包的容量c,和n個物品的重量wi價值vi,求在揹包容量允許的條件下能裝入的最大價值 1.問題分析 解空間 子集樹,第i層每個節點有兩棵子樹 選擇物品i,不選擇物品i 優先佇列的優先順序如何確定?每個節點的祖先節點都已經確定,那麼可以得到已經裝入揹包的物品的價值,加上剩...
分支限界法01揹包問題 01揹包問題
1.01揹包問題的描述 有n個不可分割的物品,它們有各自的重量和價值,現有固定容量的揹包,選擇把哪些物品放入揹包可以讓揹包中物品的價值最大。2.錯覺 按照價值和重量的比值 價效比 進行排序,依次嘗試放入直到放不進揹包為止。但是細想思考一下就能發現,這個貪心演算法是有問題的,看下面乙個例子。揹包容量1...