前言
雖然之前也用過回溯法,但總是過一段時間,又會忘記。思來想去,得出一個結論”看(做)一兩個回溯法的例子,思想仍然是別人;看(做)多個回溯法的例子,思想才能成爲自己的“,於是決定對回溯法進行一個系統的整理和學習,挖掘其精髓,整理出系統的思路和模板,以期待自己能夠對該類問題遊刃有餘。
回溯法有“通用的解題法”之稱。
1.定義:
- 也叫試探法,它是一種系統地搜索問題的解的方法。
2.基本思想:
- 從一條路往前走,能進則進,不能進則退回來,換一條路再試。
3.一般步驟:
- 定義一個解空間(子集樹、排列樹二選一)
- 利用適於搜索的方法組織解空間。
- 利用深度優先法搜索解空間。
- 利用剪枝函數避免移動到不可能產生解的子空間。
4.約束函數:
- 是否滿足顯約束(存在)
5.限界函數:
- 是否滿足隱約束(最優)
6.子集樹模板
遍歷子集樹,時間複雜度 O(2^n)
如果解的長度是不固定的,那麼解和元素順序無關,即可以從中選擇0個或多個。例如:子集,迷宮,...
如果解的長度是固定的,那麼解和元素順序有關,即每個元素有一個對應的狀態。例如:子集,8皇后,...
解空間的個數指數級別的,爲2^n,可以用子集樹來表示所有的解
適用於:冪集、子集和、0-1揹包、裝載、8皇后、迷宮、...
a.子集樹模板遞歸版
'''求集合{1, 2, 3, 4}的所有子集'''
n = 4
#a = ['a','b','c','d']
a = [1, 2, 3, 4]
x = [] # 一個解(n元0-1數組)
X = [] # 一組解
# 衝突檢測:無
def conflict(k):
global n, x, X, a
return False # 無衝突
# 一個例子
# 衝突檢測:奇偶性相同,且和小於8的子集
def conflict2(k):
global n, x, X, a
if k==0:
return False
# 根據部分解,構造部分集
s = [y[0] for y in filter(lambda s:s[1]!=0, zip(a[:k+1],x[:k+1]))]
if len(s)==0:
return False
if 0 < sum(map(lambda y:y%2, s)) < len(s) or sum(s) >= 8: # 只比較 x[k] 與 x[k-1] 奇偶是否相間
return True
return False # 無衝突
# 子集樹遞歸模板
def subsets(k): # 到達第k個元素
global n, x, X
if k >= n: # 超出最尾的元素
print(x)
X.append(x[:]) # 保存(一個解)
else:
for i in [1, 0]: # 遍歷元素 a[k] 的兩種選擇狀態:1-選擇,0-不選
x.append(i)
if not conflict2(k): # 剪枝
subsets(k+1)
x.pop() # 回溯
# 根據一個解x,構造一個子集
def get_a_subset(x):
global a
return [y[0] for y in filter(lambda s:s[1]!=0, zip(a,x))]
# 根據一組解X, 構造一組子集
def get_all_subset(X):
return [get_a_subset(x) for x in X]
# 測試
subsets(0)
# 查看第3個解,及對應的子集
#print(X[2])
#print(get_a_subset(X[2]))
print(get_all_subset(X))
打印如下:
[1, 0, 1, 0]
[1, 0, 0, 0]
[0, 1, 0, 1]
[0, 1, 0, 0]
[0, 0, 1, 0]
[0, 0, 0, 1]
[0, 0, 0, 0]
[[1, 3], [1], [2, 4], [2], [3], [4], []]
b.子集樹模板非遞歸版
7.排列樹模板
遍歷排列樹,時間複雜度O(n!)
解空間是由n個元素的排列形成,也就是說n個元素的每一個排列都是解空間中的一個元素,那麼,最後解空間的組織形式是排列樹
適用於:n個元素全排列、旅行商、...
a.排列樹模板遞歸版
'''求[1,2,3,4]的全排列'''
n = 4
x = [1,2,3,4] # 一個解
X = [] # 一組解
# 衝突檢測:無
def conflict(k):
global n, x, X
return False # 無衝突
# 一個例子
# 衝突檢測:元素奇偶相間的排列
def conflict2(k):
global n, x, X
if k==0: # 第一個元素,肯定無衝突
return False
if x[k-1] % 2 == x[k] % 2: # 只比較 x[k] 與 x[k-1] 奇偶是否相同
return True
return False # 無衝突
# 排列樹遞歸模板
def backkrak(k): # 到達第k個位置
global n, x, X
if k >= n: # 超出最尾的位置
print(x)
#X.append(x[:]) # 注意x[:]
else:
for i in range(k, n): # 遍歷後面第 k~n-1 的位置
x[k], x[i] = x[i], x[k]
if not conflict2(k): # 剪枝
backkrak(k+1)
x[i], x[k] = x[k], x[i] # 回溯
# 測試
backkrak(0)
打印如下:
[1, 2, 3, 4]
[1, 4, 3, 2]
[2, 1, 4, 3]
[2, 3, 4, 1]
[3, 2, 1, 4]
[3, 4, 1, 2]
[4, 3, 2, 1]
[4, 1, 2, 3]
b.排列樹模板非遞歸版
參考:https://www.cnblogs.com/hhh5460/p/6917963.html