簡體   English   中英

使用回溯的唯一排列

[英]Unique permutations using backtracking

我試圖在尋找唯一排列的問題中使用回溯。 我寫過這個:

def f(A, start, end):
    if start == end - 1:
        print(A)
    else:
        for idx in range(start, end):
            if idx != start and A[idx] == A[start]:
                continue
            A[idx], A[start] = A[start], A[idx]
            f(A, start + 1, end)

這個例子有效

A = [2, 3, 2]
f(A, 0, len(A))

[2, 3, 2]
[2, 2, 3]
[3, 2, 2]

這個不行

A = [2, 2, 1, 1]
f(A, 0, len(A))

[2, 2, 1, 1]
[2, 1, 2, 1]
[2, 1, 1, 2]
[2, 2, 1, 1] #unwanted duplicate!
[1, 2, 2, 1]
[1, 2, 1, 2]
[1, 1, 2, 2]
[1, 2, 2, 1]
[1, 2, 1, 2]
[2, 2, 1, 1]
[2, 1, 2, 1]
[2, 1, 1, 2]
[2, 2, 1, 1]

為什么我的結果中仍然有重復項?

在過濾中,您使用一對一檢查。 因此,作為一個結果,但不會從目前的工作有三個以上的元素

這是因為,您可以在多次(真實)交換后獲得相同的排列 例如:

[1   ,2(1),2(2),3   ] -> swap 1 with 3
[1   ,3,   2(2),2(1)] -> swap 1 with 2
[1   ,2(2),3   ,2(1)] -> swap 2 with 3
[1   ,2(2),2(1),3   ]

如您所見,排列是相同的(但兩者的起源不同)。 所以我們間接地交換了兩者。

然而,沒有必要讓它變得那么復雜。 有兩種方法可能在這里起作用:

  • 對列表進行排序,並強制執行一個約束,即您只能發出按字典順序排列的列表比以前更多的列表;
  • 首先計算出現次數(使用Counter ,然后確保根據計數器發出)。

后者將運行得更快,因為它不會生成它必須省略的排列。

一個示例實現可能是:

from collections import Counter

def f(lst):
    def g(l,c,n):
        if n <= 0:
            yield tuple(l)
        else:
            for k,v in c.items():
                if v > 0:
                    c[k] -= 1
                    l.append(k)
                    for cfg in g(l,c,n-1):
                        yield cfg
                    l.pop()
                    c[k] += 1
    for cfg in g([],Counter(lst),len(lst)):
        yield cfg

這給出:

>>> list(f([1,1,2,2]))
[(1, 1, 2, 2), (1, 2, 1, 2), (1, 2, 2, 1), (2, 1, 1, 2), (2, 1, 2, 1), (2, 2, 1, 1)]
>>> list(f([1,1,2,2,3]))
[(1, 1, 2, 2, 3), (1, 1, 2, 3, 2), (1, 1, 3, 2, 2), (1, 2, 1, 2, 3), (1, 2, 1, 3, 2), (1, 2, 2, 1, 3), (1, 2, 2, 3, 1), (1, 2, 3, 1, 2), (1, 2, 3, 2, 1), (1, 3, 1, 2, 2), (1, 3, 2, 1, 2), (1, 3, 2, 2, 1), (2, 1, 1, 2, 3), (2, 1, 1, 3, 2), (2, 1, 2, 1, 3), (2, 1, 2, 3, 1), (2, 1, 3, 1, 2), (2, 1, 3, 2, 1), (2, 2, 1, 1, 3), (2, 2, 1, 3, 1), (2, 2, 3, 1, 1), (2, 3, 1, 1, 2), (2, 3, 1, 2, 1), (2, 3, 2, 1, 1), (3, 1, 1, 2, 2), (3, 1, 2, 1, 2), (3, 1, 2, 2, 1), (3, 2, 1, 1, 2), (3, 2, 1, 2, 1), (3, 2, 2, 1, 1)]

那么你的輸入數組中有重復的元素。 這可能會導致解決方案中的元素冗余或冗余排列,但如果您在數組中使用了諸如唯一元素之類的輸入,例如...

A = [1,2,3,4...] 等等然后下面的代碼可能會有所幫助

def f(A, start, end):
if start == end - 1:
    print(A)
else:
    for idx in range(start, end):
        if idx != start and A[idx] == A[start]:
            continue
        A[idx], A[start] = A[start], A[idx]
        f(A, start + 1, end)
        A[idx], A[start] = A[start], A[idx]  #This is added

而這個例子......

A = [1, 2, 3, 4]
f(A, 0, len(A))

輸出是...

[1, 2, 3, 4]
[1, 2, 4, 3]
[1, 3, 2, 4]
[1, 3, 4, 2]
[1, 4, 3, 2]
[1, 4, 2, 3]
[2, 1, 3, 4]
[2, 1, 4, 3]
[2, 3, 1, 4]
[2, 3, 4, 1]
[2, 4, 3, 1]
[2, 4, 1, 3]
[3, 2, 1, 4]
[3, 2, 4, 1]
[3, 1, 2, 4]
[3, 1, 4, 2]
[3, 4, 1, 2]
[3, 4, 2, 1]
[4, 2, 3, 1]
[4, 2, 1, 3]
[4, 3, 2, 1]
[4, 3, 1, 2]
[4, 1, 3, 2]
[4, 1, 2, 3]

希望這對你有幫助:)

這是因為您正在“就地”將開關應用於您的列表。 :您在計算排列時正在修改列表 A。)

這是對您的代碼的快速修復:

def f(A, start, end):
     if start == end - 1:
         print(A)
     else:
         B = A.copy()
         for idx in range(start, end):
             if idx != start and B[idx] == B[start]:
                 continue
             B[idx], B[start] = A[start], A[idx]
             f(B, start + 1, end)

A = [2, 2, 1, 1]
f(A, 0, len(A))
# [2, 2, 1, 1]
# [2, 1, 2, 1]
# [2, 1, 1, 2]
# [1, 2, 2, 1]
# [1, 2, 1, 2]
# [1, 1, 2, 2]

如果您想避免由於重復數字而導致的重復,您可以先對您的數據進行排序,然后添加一個條件進行交換(僅當元素較大時):

def f_s(A, start, end):
    f(sorted(A), start, end)

def f(A, start, end):
    if start == end - 1:
        print(A)
    else:
        for idx in range(start, end):
            if idx != start and A[idx] == A[start]:
                continue
            if A[idx] >= A[start]:
                A[idx], A[start] = A[start], A[idx]
                f(A, start + 1, end)

A = [2, 3, 2]
f_s(A, 0, len(A))

A = [2, 2, 1, 1]
f_s(A, 0, len(A))

輸出:

[2, 2, 3]
[2, 3, 2]
[3, 2, 2]

[1, 1, 2, 2]
[1, 2, 1, 2]
[1, 2, 2, 1]
[2, 1, 2, 1]
[2, 2, 1, 1]

Willem 的答案的輕微變化,利用了收益和對正在發生的事情的解釋。 我們避免枚舉重復元素,但我們仍然對數據進行排序以按字典順序發出排列。

def multiset_permutation(A):

    def solve_permutation(depth, counter, permutation):
        # base case/goal
        if depth == 0:
            yield permutation
            return

        # choices
        for key, value in counter.items():
            # constraint
            if value > 0:
                # make a choice
                counter[key] -= 1
                permutation.append(key)

                # explore
                yield from [
                    list(i) for i in solve_permutation(depth - 1, counter, permutation)
                ]

                # backtrack - undo our choices
                permutation.pop()
                counter[key] += 1

    """
    Lexicographical order requires that we sort the list first so that we
    incrementally emit the next larger permutation based on the counters
    """
    A = sorted(A)
    counter = collections.Counter(A)

    return list(solve_permutation(len(A), counter, []))

輸出:

[[1, 1, 2], [1, 2, 1], [2, 1, 1]]

調用堆棧來解決[1, 1, 2]將如下所示:

depth counter permutation
0, {1:0, 2:0}, [1,1,2]
1, {1:0, 2:1}, [1,1]
2, {1:1, 2:1}, [1]
3, {1:2, 2:1}, []

0, {1:0, 2:0}, [1,2,1]
1, {1:0, 2:1}, [1,2]
2, {1:1, 2:1}, [1]

0, {1:0, 2:0}, [2,1,1]
1, {1:0, 2:1}, [2,1]
2, {1:1, 2:1}, [2]
3, {1:2, 2:1}, []

遞歸樹:

                 []
           /           \

        [1]                  [2]
      /    \                  |        
    [1,1]  [1,2]             [2,1]   
    /         \               |      

[1, 1, 2]   [1, 2, 1]      [2, 1, 1]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM