简体   繁体   English

使用回溯的唯一排列

[英]Unique permutations using backtracking

I am trying to use backtracking in the problem of finding unique permutions.我试图在寻找唯一排列的问题中使用回溯。 I have written this:我写过这个:

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)

This example works这个例子有效

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

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

This one doesn't work这个不行

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]

Why do I still have duplicates in the result?为什么我的结果中仍然有重复项?

In the filtering, you use a one-versus-one check.在过滤中,您使用一对一检查。 So as a result, but that will not work from the moment there are more than three elements .因此,作为一个结果,但不会从目前的工作有三个以上的元素

This is because, you can obtain the same permutation after several (real) swaps .这是因为,您可以在多次(真实)交换后获得相同的排列 For example:例如:

[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   ]

As you can see the permutation is the same (but the origin of the two twos is different).如您所见,排列是相同的(但两者的起源不同)。 So we swapped the two twos indirectly.所以我们间接地交换了两者。

Nevertheless, there is no need to make it that complicated.然而,没有必要让它变得那么复杂。 There are two approaches that might work here:有两种方法可能在这里起作用:

  • sort the list, and enforce a constraint that you can only emit lists that are lexicographically more than the previous;对列表进行排序,并强制执行一个约束,即您只能发出按字典顺序排列的列表比以前更多的列表; and
  • first count the occurrences (using a Counter , then make sure you emit based on the counters).首先计算出现次数(使用Counter ,然后确保根据计数器发出)。

The latter will run faster since it will not generate permutations it has to omit.后者将运行得更快,因为它不会生成它必须省略的排列。

An example implementation could be:一个示例实现可能是:

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

This gives:这给出:

>>> 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)]

Well you have duplicate elements in your input array.那么你的输入数组中有重复的元素。 This might lead to redundancy of elements or redundant permutations in your solution but if you used an input such as unique elements in your array such as...这可能会导致解决方案中的元素冗余或冗余排列,但如果您在数组中使用了诸如唯一元素之类的输入,例如...

A = [1,2,3,4...] and so on then following code might help 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

And the example for this...而这个例子......

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

The OUTPUT is...输出是...

[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]

Hope this helps you :)希望这对你有帮助:)

This is because you are applying switches to your list 'in place'.这是因为您正在“就地”将开关应用于您的列表。 ( ie : you are modifying your list A while computing permutations.) :您在计算排列时正在修改列表 A。)

This is a quickfix to your code:这是对您的代码的快速修复:

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]

If you want to avoid duplicates due to repetitive numbers, you can first sort your data, then add a condition to make the exchange (only if element is larger):如果您想避免由于重复数字而导致的重复,您可以先对您的数据进行排序,然后添加一个条件进行交换(仅当元素较大时):

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))

Output:输出:

[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]

Slight variation of Willem's answer that leverages yield from and an explanation of what's happening. Willem 的答案的轻微变化,利用了收益和对正在发生的事情的解释。 We avoid enumerating repeat elements but we still sort our data to emit permutations in lexicographic order.我们避免枚举重复元素,但我们仍然对数据进行排序以按字典顺序发出排列。

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, []))

Output:输出:

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

Call Stack to solve [1, 1, 2] will looks as follows:调用堆栈来解决[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}, []

Recursive Tree:递归树:

                 []
           /           \

        [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