一、回溯法
回溯法是一個既帶有系統性又帶有跳躍性的搜索算法。
它在包括問題的全部解的解空間樹中依照深度優先的策略,從根節點出發搜索解空間樹。算法搜索至解空間樹的任一節點時,總是先推斷該節點是否肯定不包括問題的解。假設肯定不包括。則跳過對以該節點為根的子樹的系統搜索,逐層向其原先節點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜索。
運用回溯法解題通常包括下面三個步驟:
· 針對所給問題,定義問題的解空間;
· 確定易於搜索的解空間結構。
· 以深度優先的方式搜索解空間,而且在搜索過程中用剪枝函數避免無效搜索。
二、01背包問題描寫敘述
01背包問題,即向容量為M的背包裝載物品,要么放入要么不放入。從n個物品中選取裝入背包的物品,物品i的重量為Wi。價值為Pi。最佳裝載指裝入的物品價值最高,即∑PiXi(i=1..n)取最大值。約束條件為∑WiXi≤M且Xi∈[0,1](1≤i≤n)。
在這個表達式中,需求出Xi的值。
Xi=1表示物品i裝入背包。Xi=0表示物品i不裝入背包。
· 即推斷可行解的約束條件是:∑WiXi≤M(i=0..n)。Wi>0,Xi∈[0,1](1≤i≤n)
· 目標最大值:max∑PiXi(i=0..n-1),Pi>0,Xi=0或1(0≤i<n)
0/1背包問題是一個自己選取問題,適合於用子集樹表示0/1背包問題的解空間。
在搜索解空間樹時,僅僅要左兒子節點是一個可行節點。搜索就進入左子樹。在右子樹中有可能包括最優解才進入右子樹搜索,否則將右子樹剪去。
三、關於剪枝函數
設當前剩余物品價值總和為r。當前結點x價值為cp。當前x結點上界函數值為bp。
L為當前已搜索到的答案結點中受益的最大值。當cp+r=bp<L時可剪去以X為根的子樹。
計算右子樹中解上界方法是將剩余物品按單位重量價值排序,一次放入物品直至裝不下為止,再裝入部分未裝入物品直至裝滿背包。由此得到的價值是右子樹解上界。
四、遞歸實現
例如以下圖1所看到的為01背包問題遞歸實現的示意圖。圖2是01背包問題遞歸實現的流程圖。描寫敘述了代碼實現方案。
圖1 01背包問題遞歸描寫敘述圖 圖2 01背包問題遞歸實現流程圖
圖1比較easy理解,是否已拿完物品也就是i<n(i是當前物品序號。n是物品總數目)是否成立,假設成立則遞歸結束並打印輸出路徑。
假設i<n則推斷第i個物品是否能放入背包,假設不能放入。則考慮放置i+1個物品。假設能放入還存在當前第i個放入和不放入兩種情形后對第i+1個的影響。
注意在“放入還是不放入”的部分可考慮增加剪枝函數。
五、遞歸的代碼實現
代碼1 Main函數測試代碼:
public static void Main (string[] args)
{
Obj[] objs = new Obj[4];
objs[0] = new Obj() { Weight = 7, Price = 42 };
objs[1] = new Obj() { Weight = 3, Price = 12 };
objs[2] = new Obj() { Weight = 4, Price = 40 };
objs[3] = new Obj() { Weight = 5, Price = 25 };
Backtracking_Recursion1 r = new Backtracking_Recursion1();
r.W = 10;
r.objs = objs;
r.Backtracking(0);
Console.Read();
}
代碼2Obj物品代碼
public class Obj
{
/// <summary>
/// 物品重量
/// </summary>
public int Weight
{
get;
set;
}
/// <summary>
/// 物品價值
/// </summary>
public int Price
{
get;
set;
}
/// <summary>
/// 該物品是否放入包內
/// </summary>
internal bool Selected
{
get;
set;
}
}
代碼3遞歸實現01背包問題
class Backtracking_Recursion1 { #region field protected int m_currentWeight = 0; protected int m_currentPrice = 0; #endregion #region property /// <summary> /// 背包容量 /// </summary> /// <value>默認20</value> public int W { get; set; } public int n { get { return objs == null ?
0 : objs.Length; } } /// <summary> /// 物品,包括重量/價值和數量 /// </summary> /// <value>The objects.</value> public Obj[] objs { get; set; } #endregion public void Backtracking(int i) { if (i >= n) { Printing(); return; } if (objs[i].Weight + m_currentWeight <= W) { m_currentWeight += objs[i].Weight; m_currentPrice += objs[i].Price; objs[i].Selected = true; Backtracking(i + 1); m_currentPrice -= objs[i].Price; m_currentWeight -= objs[i].Weight; } objs[i].Selected = false; Backtracking(i + 1); } /// <summary> /// 輸出路徑 /// </summary> protected void Printing() { Console.Write("<"); for (int i = 0; i < objs.Length; i++) { Console.Write(objs[i].Selected ?
"1 " : "0 "); } Console.WriteLine(">, price: " + m_currentPrice.ToString() + "\t weight: " + m_currentWeight.ToString()); } }
六執行結果
注:
1 代碼3中Printing()函數調用后可推斷並記錄最優路徑;
2 下文將講述01背包問題回溯法的順序執行方法。並通過模板模式整合兩種不同的實現方案。