【问题标题】:Generating permutations of a set - efficiently and with distinction生成集合的排列 - 高效且有区别
【发布时间】:2016-01-24 13:47:26
【问题描述】:

我正在构建来自 here 的代码。我想生成一组的所有排列,例如(取自线程):

Collection: 1, 2, 3
Permutations: {1, 2, 3}
              {1, 3, 2}
              {2, 1, 3}
              {2, 3, 1}
              {3, 1, 2}
              {3, 2, 1}

每个集合都有 可能的排列,但这不是我想要实现的。请考虑以下集合:

这将产生 排列,极端数量。这将需要非常长的时间来计算,因为每个零都被认为是唯一的。

除此之外,我只想生成不同的排列。如果我们这样做,只有

排列remaining,因为 18 个项目是相同的 (k)。

现在,我可以从上述线程运行代码并将结果存储在 HashSet 中,从而消除重复排列。然而,这将是极其低效的。我正在寻找一种算法来直接生成有区别的排列。

【问题讨论】:

  • 我很困惑。链接线程中的代码正在执行字典增量,这实际上适用于具有重复元素的多重集,不需要 HashSet。
  • 好吧,我想说的是,您需要做的就是在应用算法之前使用输入的不同子集。
  • @DavidEisenstat 它工作正常,但计算需要很长时间。如果我不使用 HashSet,我会得到 2.4 * 10^18 个结果。
  • 不,我的意思是生成的排列是成对不同的。

标签: c# algorithm performance recursion permutation


【解决方案1】:

使用 Swap 算法查找排列,您可以直接排除产生重复排列的部分。该算法可在 Internet 上找到,您可以找到有关它的更多信息。

private static void Main(string[] args)
{
    List<int> set = new List<int>
    {
        20, 4, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    var permutes = Permute(set);

    Console.WriteLine(permutes.Count); // outputs 58140
}

private static List<int[]> Permute(List<int> set)
{
    List<int[]> result = new List<int[]>(); 

    Action<int> permute = null;
    permute = start =>
    {
        if (start == set.Count)
        {
            result.Add(set.ToArray());
        }
        else
        {
            List<int> swaps = new List<int>();
            for (int i = start; i < set.Count; i++)
            {
                if (swaps.Contains(set[i])) continue; // skip if we already done swap with this item
                swaps.Add(set[i]);

                Swap(set, start, i);
                permute(start + 1); 
                Swap(set, start, i);
            }
        }
    };

    permute(0);

    return result;
}

private static void Swap(List<int> set, int index1, int index2)
{
    int temp = set[index1];
    set[index1] = set[index2];
    set[index2] = temp;
}

这是显示交换算法如何工作的图像。

所以你有{A,B,C}, {A,C,B}, {B,A,C}, {B,C,A}, {C,B,A}, {C,A,B}

现在考虑AB 相等。我用 Photoshop 编辑了图像(对不起,如果我不擅长它!)并用A 替换了B。如图所示

我在图像中发现了重复项。如果您跳过它们,您将拥有{A,A,C}, {A,C,A}, {C,A,A}

您必须存储被交换的项目,因此如果项目相等并且我们已经进行了交换,我们只需跳过以防止重复

if (swaps.Contains(set[i])) continue; // skip if we already done swap with this item
swaps.Add(set[i]); // else add it to the list of swaps.

为了测试,如果你删除这部分,那么这个算法会产生重复的排列,控制台会输出n!

【讨论】:

  • 我已经用 Permute(new List() {20, 4, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),但是它会触发 OutOfMemoryException 因为列表将增长超过 1000 万(实际结果应该是 58410)
  • @Jacobus21 感谢您提供的信息。我通过记住交换来修复它。现在测试它;)
  • 太棒了!置换 { 20, 4, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } 大约需要 28 秒,其中比我的方法快两秒。有没有办法进一步优化?
  • 我的错,我正在将结果写入一个列表,这大大减慢了速度。真的很快!
【解决方案2】:

这是迄今为止我想出的最佳解决方案。欢迎提出优化建议。它准确地返回 n!/k!项目。

置换 { 20, 4, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 大约需要一秒钟}。

private static IEnumerable<int[]> Permute(int[] list)
{
    if (list.Length > 1)
    {    
        int n = list[0];
        foreach (int[] subPermute in Permute(list.Skip(1).ToArray()))
        {    
            for (int index = 0; index <= subPermute.Length; index++)
            {
                int[] pre = subPermute.Take(index).ToArray();
                int[] post = subPermute.Skip(index).ToArray();

                if (post.Contains(n))
                    continue;

                yield return pre.Concat(new[] { n }).Concat(post).ToArray();
            }    
        }
    }
    else
    {
        yield return list;
    }
}

【讨论】:

  • 令人印象深刻!感谢您提供大约时间
【解决方案3】:

让我们试试吧:

Knuths
1. Find the largest index j such that a[j] < a[j + 1]. If no
such index exists, the permutation is the last permutation.

{20,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
   >  > = = = = = = = = = = = = = = = = =

糟糕,没有索引ja[j] &lt; a[j + 1] 相同。但是由于我们想要所有个不同的排列,我们可以对每个数组进行排序并保证我们从头开始:

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,20}

1. Find the largest index j such that a[j] < a[j + 1]. If no
such index exists, the permutation is the last permutation.

j = 18 since a[18] < a[19]

2. Find the largest index l such that a[j] < a[l]. Since j + 1
is such an index, l is well defined and satisfies j < l.

l = 19 since a[18] < a[19]

3. Swap a[j] with a[l].

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,10}

4. Reverse the sequence from a[j + 1] up to and including the final element a[n].

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,10}

让我们再做一些:

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,10}
yields {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,20}
yields {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,20,0}
yields {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,10}
yields {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,10,0}
yields {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,20}
...

如您所见,大元素稳步(且明显)向左移动,直到:

{10,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
yields {20,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}

并且在没有重复排列的情况下结束。

【讨论】:

    【解决方案4】:

    我以前做过这个问题。您只需要先对数组进行排序,然后使用 boolean[] 访问过的数组来标记您访问过的元素。我在 Java 中使用回溯的解决方案:

    public class Solution {
        public List<List<Integer>> permuteUnique(int[] num) {
            List<List<Integer>> result = new ArrayList<List<Integer>>();
            List<Integer> list = new ArrayList<Integer>();
            boolean[] visited = new boolean[num.length];
    
            Arrays.sort(num);
            helper(result, list, num, visited);
    
            return result;
        }
    
        private void helper(List<List<Integer>> result, List<Integer> list,
                int[] num, boolean[] visited) {
            for (int i = 0; i < num.length; i++) {
                if (visited[i]
                        || (i > 0 && num[i] == num[i - 1] && !visited[i - 1])) {
                    continue;
                }
                list.add(num[i]);
                visited[i] = true;
                if (list.size() == num.length) {
                    result.add(new ArrayList<Integer>(list));
                } else {
                    helper(result, list, num, visited);
                }
                list.remove(list.size() - 1);
                visited[i] = false;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-06-27
      • 2016-08-05
      • 2010-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-02
      • 2010-11-05
      相关资源
      最近更新 更多