【问题标题】:Unique permutations using backtracking使用回溯的独特排列
【发布时间】:2020-05-25 15:55:33
【问题描述】:

我试图在寻找唯一排列的问题中使用回溯。我写了这个:

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]

为什么结果中仍有重复项?

【问题讨论】:

  • 好吧,你有A = [2,2,1,1],在f,你也会调用f([2,1,2,1], 1, len(A),两种情况都可以输出[2,2,1,1](第一个没有排列,第二个是索引1的元素之间的排列& 2)

标签: python permutation backtracking


【解决方案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)]

【讨论】:

    【解决方案2】:

    您的输入数组中有重复的元素。这可能会导致您的解决方案中的元素冗余或冗余排列,但如果您使用数组中的唯一元素等输入,例如......

    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]
    

    希望对你有帮助:)

    【讨论】:

    • “你的输入数组中有重复的元素” - 这是需要的,它是一个多重集。
    【解决方案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]
    

    【讨论】:

      【解决方案4】:

      如果你想避免因为重复的数字造成重复,你可以先对你的数据进行排序,然后添加一个条件进行交换(仅当元素较大时):

      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]
      

      【讨论】:

        【解决方案5】:

        Willem 的 answer 略有变化,它利用了 yield from 并解释了正在发生的事情。我们避免枚举重复元素,但我们仍然对数据进行排序以按字典顺序发出排列。

        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]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-05-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-23
          • 1970-01-01
          相关资源
          最近更新 更多