1. 程式人生 > >演算法47--回溯法4--演算法總結

演算法47--回溯法4--演算法總結

之前用回溯法解決了子集,排列,組合等問題,現在總結一下回溯演算法:

在包含問題的所有解的解空間樹中,按照深度優先搜尋的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去,如果該結點不包含問題的解,則逐層向其祖先結點回溯。(其實回溯法就是對隱式圖的深度優先搜尋演算法)。 若用回溯法求問題的所有解時,要回溯到根,且根結點的所有可行的子樹都要已被搜尋遍才結束。 而若使用回溯法求任一個解時,只要搜尋到問題的一個解就可以結束。

回溯法實際是對於一顆樹的深度優先搜尋,在每次搜尋一種狀態時都要判斷該種狀態是否滿足條件,如果已經不滿足,則可以直接剪枝處理;如果當前狀態不滿足,下一個狀態可能滿足,則繼續搜尋下一個狀態。

其實現要點如下:

1.根節點的確定

2.從某一節點出發的子節點的可能情況

3.判斷當前狀態是否符合條件

4.判斷是否可以剪枝

5.根據當前節點的子節點情況進行遍歷,選取第一個子節點,儲存下一個狀態,遞迴下一個狀態判斷,回退到當前狀態,進行第二個節點判斷,依次進行直到所有子節點遍歷完成

6.去重的判斷:一般將某一節點的所有子節點排序,然後遍歷某一子節點,如果前一個節點與本節點相同,則可以直接跳過,搜尋下一個子節點

Given a set of distinct integers, nums, return all possible subsets (the power set).

class Solution:
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        rr=[]
        self.backsubsets(rr, [], 0, nums)   
        return rr
    
    def backsubsets(self, rr, r, index, nums):
        rr.append(r.copy())
        print(rr)
        for i in range(index, len(nums)):
            r.append(nums[i])
            self.backsubsets(rr, r, i+1, nums)
            r.pop(-1)
        

深度優先搜尋樹如下所示:

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

class Solution:
    def subsetsWithDup(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        rr=[]
        nums.sort()
        self.backtrack(rr, [], 0, nums)
        return rr
    
    def backtrack(self, rr, r, index, nums):
        rr.append(r.copy())
        for i in range(index, len(nums)):
            if i>index and nums[i]==nums[i-1]:
                continue
            r.append(nums[i])
            self.backtrack(rr, r, i+1, nums)
            r.pop(-1)

要注意去重,當遍歷到某一節點,分析其子節點的各自取值時,由於原陣列已經排序,因此相同元素此時已經相鄰,分析第一個子節點直接進入下一個狀態判斷,分析第二個子節點時,如果第二個子節點和第一個相同,則應該直接跳過第二個子節點,因此去重的判斷條件是

i>index and nums[i-1]==nums[i]

深度優先搜尋樹:

Given a collection of distinct integers, return all possible permutations.

class Solution:
    def permute(self, nums=[1,2,3]):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        rr = []
        self.backtrace(rr, [], nums)
        return rr
    
    def backtrace(self, rr, r, nums):
        if len(r)==len(nums):
            rr.append(r.copy())
            return
        for i in range(0, len(nums)):
            if nums[i] in r:
                continue
            r.append(nums[i])
            self.backtrace(rr, r, nums)
            r.pop(-1)

深度優先搜尋樹:

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

class Solution:
    def permuteUnique(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        rr = []
        flag = [False for v in range(len(nums))]
        nums.sort()
        self.backtrace(rr, [], flag, nums)
        return rr
    
    def backtrace(self, rr, r, flag, nums):
        if len(r)==len(nums):
            rr.append(r.copy())
            return
        for i in range(0, len(nums)):
            if flag[i] or (i>0 and nums[i-1]==nums[i] and flag[i-1]):
                continue
            r.append(nums[i])
            flag[i] = True
            #print(r)
            self.backtrace(rr, r, flag, nums)
            r.pop(-1)
            flag[i] = False
        

深度優先搜尋樹:

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.The same repeated number may be chosen from candidates unlimited number of times.

class Solution:
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        rr=[]
        self.backtrace(rr, [], 0, candidates, target, 0)
        return rr

    def backtrace(self, rr, r, s, candidates, target, start):
        if s==target:
            rr.append(r.copy())
            return
        if s>target:
            return
        for i in range(start, len(candidates)):
            r.append(candidates[i])
            s += candidates[i]
            self.backtrace(rr, r, s, candidates, target, i)
            s -= candidates[i]
            r.pop(-1)

深度優先樹:

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.Each number in candidates may only be used once in the combination.

class Solution:
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        rr=[]
        candidates.sort()
        self.backtrace(rr, [], 0, candidates, target, 0)
        return rr
        
    def backtrace(self, rr, r, s, candidates, target, start):
        if s == target:
            rr.append(r.copy())
            return
        if s > target:
            return
        for i in range(start, len(candidates)):
            if i>start and candidates[i-1]==candidates[i]:
                continue
            r.append(candidates[i])
            #print(r)
            s += candidates[i]
            #print(s)
            self.backtrace(rr, r, s, candidates, target, i+1)
            r.pop(-1)
            s -= candidates[i]

Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.

class Solution:
    def combinationSum3(self, k, n):
        """
        :type k: int
        :type n: int
        :rtype: List[List[int]]
        """
        rr=[]
        self.backtrace(rr, [], 0, k, 1, n)
        return rr
        
    def backtrace(self, rr, r, s, k, start, n):
        if len(r)==k and s==n:
            rr.append(r.copy())
            return      
        if s>n or len(r)>k or (len(r)==k and s!=n):
            return                
        for i in range(start, 10):
            r.append(i)            
            s += i
            self.backtrace(rr, r, s, k, i+1, n)
            r.pop(-1)
            s -= i

回溯法實現了對於集合按照某種規則進行遍歷所有情況,當遍歷到某一種情況時要判斷搜尋條件是否符合,以及根據當前節點判斷其子節點的情況。

回溯法一般處理步驟:

宣告儲存所有樹節點狀態集合rr

宣告用來儲存搜尋過程中的節點儲存變數r

確定根節點,一般從空集開始[]

判斷當前狀態r是否符合條件,符合則複製r一份加入到rr中

如果不符合,並且繼續搜尋子節點也必定不符合,則直接返回

分析當前節點的左右可能子節點:

      根據子節點的取值,判斷是否去重,是否直接進行下一個子節點的搜尋

      將子節點加入到r中,更新相關變數

      進行子節點的遞迴搜尋

      將子節點從r中刪除,進行下一個子節點的分析。