目錄實驗總結
本實驗要求基於演算法設計與分析的一般過程(即待求解問題的描述、演算法設計、演算法描述、演算法正確性證明、演算法分析、演算法實現與測試),通過回溯法的在實際問題求解實踐中,加深理解其基本原理和思想以及求解步驟。求解的問題為0-1揹包。
作為挑戰:可以考慮回溯法在其他問題(如最大團問題、旅行商、圖的m著色問題)。
給定n種物品和乙個揹包。物品\(i\)的重量是\(w_i\) ,其價值為\(v_i\),揹包的容量為w。一種物品要不全部裝入揹包,要不不裝入揹包,不允許部分物品裝入的情況。裝入揹包的物品的總重量不能超過揹包的容量,在這種情況下,問如何選擇轉入揹包的物品,使得裝入揹包的物品的總價值最大?需要採用回溯的方法進行問題的求解。
分析:
(1)問題的解空間:
將物品裝入揹包,有且僅有兩個狀態。第\(i\)種物品對應\((x_1,x_2,...,x_n)\),其中\(x_i\)可以取0或1,分別代表不放入揹包和放入揹包。解空間有\(2^n\)種可能解,也就是\(n\)個元素組成的集合的所有子集的個數。採用一顆滿二叉樹來講解空間組織起來,解空間樹的深度為問題的規模\(n\)。
(2)約束條件:
\[\sum_^nw_ix_i \le w
\](3)限界條件:
0-1揹包問題的可行解不止乙個,而目標是找到總價值最大的可行解。因此需要設定限界條件來加速找出最優解的速度。如果當前是第t個物體,那麼1-t物體的狀態都已經被確定下來,剩下就是t+1~n物體的狀態,採用貪心演算法計算當前剩餘物品所能產生的最大價值是否大於最優解,如果小於最優解,那麼被剪枝掉。
採用回溯法進行問題的求解,也就是具有約束函式/限界條件的深度優先搜尋演算法。
採用回溯法特有框架:
回溯演算法()
如果到達邊界:
記錄當前的結果,進行處理
如果沒有到達邊界:
如果滿足限界條件:(左子樹)
進行處理
進行下一層的遞迴求解
將處理回退到處理之前
如果不滿足限界條件:(右子樹)
進行下一層遞迴處理
描述演算法。希望採用源**以外的形式,如偽**或流程圖等;
偽**:
遞迴式回溯演算法:
backtrack-rec(t) //t為擴充套件結點在樹中所處的層次
if t > n //已到葉子結點,輸出結果
output(x)
//檢查擴充套件結點的每個分支。s(n,t)與e(n,t)分別為當前擴充套件結點處未搜尋過
//的子樹的起始編號和終止編號
else
for i from s(n,t) to e(n,t)
x[t] = h(i) // h[i]: 在當前擴充套件結點處 x[t]的第i個可選值
if constraint(t) && bound(t) //約束函式與限界函式
backtrack-rec(t+1) //進入t+1層搜尋
迭代式回溯演算法:
backtrack-ite()
t = 1 //t為擴充套件結點在樹中所處的層次
while t > 0:
if s(n,t) <= e(n,t)
for i from s(n,t) to e(n,t)
do x[t] = h(i) //h[i]: 在當前擴充套件結點處x[t]的第i個可選值
if constrint(t) && bound(t) //滿足約束限界條件
if t > n //已到葉子結點,輸出結果
output(x);
else
t ++ //前進到更深層搜尋
else
t -- //回溯到上一層的活結點
演算法的正確性證明。需要這個環節,在理解的基礎上對演算法的正確性給予證明
回溯演算法適用條件:多公尺諾性質
假設解向量是n維的,則下面的k滿足:\(0為解的部分向量可以推得\(p(x_1,x_2,x_3,…,x_k)\)也為解的部分向量在0-1揹包問題中,解空間為:\((x_1,x_2,...,x_n)\), 如果當前結果\(p_1 = (x_1,x_2,...,x_n)\)是最優解,那麼\(p_2=(x_1,x_2,...,x_)\)的時候,也就是減少乙個物品但不改變揹包容量的時候,可以想到\(p_2\)依然是該問題的最優解。從子集樹角度來看,也就是最後一層結點全部去掉後的結果,那麼當前結果也是最優的。
演算法複雜性分析,包括時間複雜性和空間複雜性;
演算法的複雜性分析:
時間複雜度:$$ t(n)=o(2n)+o(n2n)+o(nlog(n)) = o(n2^n)$$
空間複雜度:$$o(nlog(n))$$
演算法實現與測試。附上**或以附件的形式提交,同時貼上演算法執行結果截圖;
# -*- coding: utf-8 -*-
"""created on mon oct 22 08:49:13 2018
@author: pprp
"""bv=0 # best value
cw=0 # current weight
cv=0 # current value
bx=none # best x result
def output(x):
for i in x:
print(" ",i,end="")
print()
class node(object):
def __init__(self,v,w):
self.v = v
self.w = w
self.per = float(v)/float(w)
def bound(t):
print("bound:",t)
lc = c-cw # left c
b = bv # best value
#sort
nodes =
for i in range(n):
nodes.sort(key=lambda x:x.per,reverse=true)
# 裝入揹包
while t < n and w[t] <= lc:
lc -= w[t]
b += v[t]
t += 1
if t < n:
b += float(v[t])/float(w[t]) * lc
return b
def backtrack(t,n):
"""當前在第t層"""
print("current:",t)
global bv,cv,cw,x,bx
if t >= n:
if bv < cv:
bv=cv
bx=x[:]
else:
if cw+w[t] <= c: # 搜尋左子樹,約束條件
x[t]=true
cw += w[t]
cv += v[t]
backtrack(t+1,n)
cw -= w[t]
cv -= v[t]
if bound(t) > bv: # 搜尋右子樹
x[t]=false
backtrack(t+1,n)
if __name__ == "__main__":
n=10
c=10
x=[false for i in range(n)]
w=[2,2,6,5,4,4,3,4,6,3]
v=[6,3,5,4,6,2,8,3,1,7]
backtrack(0,n)
print("best value :",bv)
print("best choice:",bx)
執行結果:
驗證:6+3+8+7=24
回溯法的思想:
能進則進,不進則換,不換則退.
回溯演算法的框架:
以dfs的方式進行搜尋,在搜尋的過程中用剪枝條件(限界函式)避免無效搜尋。約束函式,在擴充套件結點處剪去得不到可行解的子樹;限界函式:在擴充套件結點處剪去得不到最優解的子樹。
回溯演算法求解問題的一般步驟:
1、 針對所給問題,定義問題的解空間,它至少包含問題的乙個(最優)解。
2 、確定易於搜尋的解空間結構,使得能用回溯法方便地搜尋整個解空間 。
3 、以深度優先的方式搜尋解空間,並且在搜尋過程中用剪枝函式避免無效搜尋。
常用剪枝函式:子集樹、滿m叉樹、排列樹區別:子集樹:從n個元素的集合s中找到滿足某種性質的子集時,相應的解空間樹就成為了子集樹(典型問題:01揹包問題)用約束函式在擴充套件結點處剪去不滿足約束的子樹;
用限界函式剪去得不到最優解的子樹。
滿m叉樹:所給問題中每乙個元素均有m中選擇,要求確定其中的一種選擇,使得對這n個元素的選擇結果組成的向量滿足某種性質(經典問題:圖的m著色問題)
排列樹:從n個元素的排列樹中找出滿足某種性質的乙個排列的時候,相應的解空間樹稱為排列樹(經典問題:tsp問題,n皇后問題)
演算法實驗4《回溯法》
1.編寫乙個簡單的程式,解決8皇后問題。include using namespace std bool backtrack int list 8 int t return false intmain 2.批處理作業排程問題 問題描述 給定n個作業的集合j j1,j2,jn 每乙個作業ji都有兩項任...
演算法實驗二 回溯法 0 1揹包問題
時限 1000ms 記憶體限制 10000k 總時限 3000ms 描述需對容量為c 的揹包進行裝載。從n 個物品中選取裝入揹包的物品,每件物品i 的重量為wi 價值為pi 對於可行的揹包裝載,揹包中物品的總重量不能超過揹包的容量,最佳裝載是指所裝入的物品價值最高。輸入多個測例,每個測例的輸入佔三行...
演算法入門(4) 回溯法
1 概念 回溯演算法實際上乙個類似列舉的搜尋嘗試過程,主要是在搜尋嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就 回溯 返回,嘗試別的路徑。回溯法是一種選優搜尋法,按選優條件向前搜尋,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術...