回溯法是一种技术,有点像分割-合并方法。一般而言,回溯法在优化问题中很有用,可以很快的找到有效解。
对于回溯法的抽象,我们可以将问题的解定义成一个n元组(x1,x2,…xn),其中每个xi都是有限集Si中的元素。因此,抽象的回溯问题,我们可以定义如下:
1. 找到一个向量,能够使目标函数P(x1,x2,…,xn)达到最优。
2. 找到一个向量,能够满足一个特定标准函数P(x1,x2,…,xn)的向量。
在回溯问题中,比较难的就是找到约束函数,约束函数可以是显式的,也可以是隐式的。
1. 显式约束:比如说,每个元素的约束值;
2. 隐式约束:像是内部约束。
比较典型的回溯问题,如8皇后问题,问题的具体描述这里就不再赘述。
1. 一个8x8的表格,解集可以定义为一个8元组(x1,x2,…,x8),比如,一个正确的解是(4,6,8,2,7,1,3,5)
2. 显式约束像是xi Î Si,Si={1,2,3,4,5,6,7,8}, 1≤ i≤ 8,隐示约束像是任意两个不能在同一行或是同一列或同一斜线。
通过总结,我们可以建立一个状态空间树,进而通过DFS搜索来找到一个可行解,但是暴力式的搜索不是办法,可以使用回溯。
状态空间树中的术语:问题状态,状态空间,状态空间树,解状态,答案状态,
状态树中的搜索:alivenode, E-node , dead-node
在遍历过程中,回溯法不同于暴力法的是,在每个E-node中通过使用约束来杀死alive node,这样就不用再往下接着查找。
回溯算法伪代码如下所示:
BACKTRACKING(n)
kß1;
while k>0 do
if there are uncheckedX(k), X(k)∈T(X(1),…,X(k-1)) and B(X(1),…,X(k))=true
then if (X(1),…,X(k) is an answer)
then print (X(1),…,X(k))
if (k < n)
kßk+1
else
kßk-1
约束解是X(1:n),一旦确认为可行解,就将其打印出来。
T(X(1),…,X(k-1)):已知X(1),…,X(k-1).后,返回所有可能的X(k)
B(X(1),…,X(k)):返回X(1),…,X(k-1).是否满足隐示约束。
n-Queen问题的回溯算法如下所示:
NQUEENS(n)
X(1)ß0;kß1
while k>0 do
X(k)ßX(k)+1
while X(k) ≤ nand not PLACE(k) do
X(k)ßX(k)+1
if X(k) ≤ n
then if k=n
thenprint (X)
else kßk+1;X(k)ß0
elsekßk-1;
PLACE(k)
iß1
while i<k do
if X(i)=X(k) or|X(i)-X(k)| = |i-k|
thenreturn false
ißi+1
return true
第二个经典问题,subset-sum问题
问题描述如下:
• Given n+1 positive integers: wi , 1≤i≤n,and M , Find all the subsets of W={wi} , of which thesummary equals to M.
– E.g. n=4, (wl,w2,w3,w4)=(11,13,24,7),M=31
» The expected subsets are (11,13,7) and (24,7).
• The form of solution
– A solution of subset-sumproblem is defined as an n-tuple (x1,x2,… ,xn), where xiÎ{0,1}, 1≤i≤n.If wi is included in the subset, then xi=1, otherwisexi=0.
» The above answers can bedefined as (1,1,0,1)and (0,0,1,1)。
在这里,引入了递归的回溯方法。递归回溯的伪代码如下所示,其中,W(i)已经按大小顺序做了排序。
RECURSIVE-BACKTRACKING( k )
for each X(k), X(k)∈T(X(1),…,X(k-1)) andB(X(1),…,X(k))=true do
if (X(1),…,X(k)is an answer)
then print (X(1),…,X(k))
if (k<n)
then call RECURSIVE-BACKTRACKING( k+1 )
针对该问题,递归回溯算法的伪代码如下所示:
ORIGINAL-SUMOFSUB(k)
X(k)ß1
if
then print(X(j),jß1 tok)
else if
thencall SUMOFSUB(k+1)
X(k)ß0
if
then callSUMOFSUB(k+1)
SUMOFSUB(s,k,r)
X(k)ß1
if s+W(k)=M
then print(X(j),jß1 tok)
else if s+W(k)+W(k+1) ≤ M
thencall SUMOFSUB(s+W(k),k+1,r-W(k))
if s+r-W(k) ≥ M ands+W(k+1) ≤ M
then X(k)ß0
callSUMOFSUB(s,k+1,r-W(k))
那为什么n-queen问题和subsum问题,一个是非递归,而另一个是递归呢。
第三个比较经典的的例子就是0-1背包问题,对于0-1背包问题,难点在于不是找到可行解,而且还要找到最优解。这样,问题的关键就是如何找到约束函数。
约束函数的主要作用就是杀掉alive-node。
BOUND( p,w,k,M )
bßp; cßw
for ißk+1 to n do
cßc+W(i)
if c<M then bßb+P(i)
else return(b+(1-(c-M)/W(i))*P(i))
return (b)
BKNAP1( M,n,W,P,fw,fp,X )
cwßcpß0;kß1;fpß-1
loop
while k≤ n and cw+W(k)≤ M do
cwßcw+W(k);cpßcp+P(k);Y(k)ß1;kßk+1
if k>n then fpßcp;fwßcw;kßn;XßY
else Y(k)ß0
while BOUND(cp,cw,k,M)≤ fp do
while k¹0 andY(k)¹1 do
kßk-1
if k=0 thenreturn
Y(k)ß0;cwßcw-W(k);cpßcp-P(k)
kßk+1
BOUND函数用于判定当前按照贪婪方法将背包装满(假设可以装物品的一部分),在BKNAP1函数中,如果当前alive-node节点的BOUND函数不如之前的叶子结点的最终fp,那么,我们就可以将这个alive-node杀掉。这也算是对回溯算法的约束函数。