回溯法概述

前言

雖然之前也用過回溯法,但總是過一段時間,又會忘記。思來想去,得出一個結論”看(做)一兩個回溯法的例子,思想仍然是別人;看(做)多個回溯法的例子,思想才能成爲自己的“,於是決定對回溯法進行一個系統的整理和學習,挖掘其精髓,整理出系統的思路和模板,以期待自己能夠對該類問題遊刃有餘。

 

回溯法有“通用的解題法”之稱。

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章