【问题标题】:All possible pairs所有可能的对
【发布时间】:2013-10-19 13:36:06
【问题描述】:

给定一组数字,从 1 到 n,我需要对所有可能对的所有集合进行建模。

换句话说,一个绘制集合由所有数字组成。数字是成对的。如果计数是奇数 - 一个数字的条目是允许的(实际上它是必需的)。集合需要是唯一的,即对 [1,2] 与 [2,1] 相同(编辑:和解决方案:[1,2] [3,4] 与 [3,4] [1, 2])。

例如,当 n 等于 5 时,可以创建以下集合:

[1,2] [3,4] [5]
[1,2] [3,5] [4]
[1,2] [4,5] [3]
[1,3] [2,4] [5]
[1,3] [2,5] [4]
[1,3] [4,5] [2]
....

我几乎可以对解决方案进行建模,但唯一性约束对我来说很难实现。

此外 - 我觉得我的解决方案缺乏性能。我现在问题空间很大,但对我来说,即使是 n=12 的计算也需要 5 分钟以上。

public void compute(HashSet<Number> numbers, ArrayList<Pair> pairs) {
    if (numbers.size() <= 1) {
        print(pairs);
    } else {
        for (Number number1 : numbers) {
            for (Number number2 : numbers) {
                if (number1 != number2) {
                    Set<Number> possibleNumbers = new HashSet<Number>(numbers);
                    List<Pair> allPairs = new ArrayList<Pair>(pairs);

                    possibleNumbers.remove(number1);
                    possibleNumbers.remove(number2);
                    allPairs.add(new Pair(number1, number2));

                    compute(possibleNumbers, allPairs);
                }
            }
        }
    }
}

compute(numbers, new ArrayList<Pair>());

对于 n=3,我得到双倍的解决方案:

[0 1] [2] 
[0 2] [1] 
[1 0] [2] 
[1 2] [0] 
[2 0] [1] 
[2 1] [0]

那么:我应该如何解决这个问题以消除重复。如何改进实施以加快处理速度?

【问题讨论】:

  • 您有实际问题吗?
  • 问了。谢谢指点。
  • 对的顺序重要吗? [1 2] [3 4][3 4] [1 2] 一样吗?
  • 没关系。两种解决方案都是一样的
  • “一组从 1 到 n 的数字”是指没有重复项吗?即 1 到 5 总是 1,2,3,4,5 而不是 1,1,3,4,5?

标签: java math combinations probability


【解决方案1】:

你可以做两件事:

1) 坏事:

您可以继续当前的方法,但是在添加对时,您需要:

a) 确保每一对总是按某种顺序排列,例如总是[smallerNumber, biggerNumber]

b) 保持 allPairs 列表排序

然后,当您完成计算时,删除重复项将是微不足道的。这是一种不好的方法,因为它会非常慢。

2) 您可以使用不同的算法:

我在这里做两个假设:

  • 这是一个集合,所以我们没有重复项
  • 它们可以很容易地排序,即对于 1 到 5,我们知道它将是 1、2、3、4、5(但它也应该适用于 1、3、5、6 的情况)

基本上我们会有一个递归算法(就像你的一样)。输入将是“数字”-> 一个有序(升序)列表,输出将是一组对列表。让我们再次将此方法称为 compute(List numbers)。

-> 输入列表“numbers”有 1 或 2 个元素时的基本情况。在这种情况下,返回一个包含一个列表的集合,该列表包含一个包含该 1 个元素或两者的“对”。 IE。 numbers = [2,3] 或 numbers = [2] 然后您返回一个集合,其中包含一个列表,其中包含一个 (2,3) 或 (3) 的“对”。

-> 对于列表“numbers”中包含第一个元素的每对数字(即在数字 = [1,2,3,4,5] 的第一级递归中,它将是 [1,2] , [1,3], [1,4], [1,5], [1,6]) 调用Set&lt;List&lt;Pair&gt; result = compute(numbers.minus(currentPair))。遍历该集合并在每个元素的开头添加当前对。

例子:

result = empty
numbers = [1,2,3,4,5]
first = (1,2), compute([3,4,5])
    first = (3,4), compute([5])
        return (5)
    result = result + [(3,4)(5)]
    first = (3,5) compute([4])
        return (4)
    result = result + [(3,5),(4)] (a this point result is [ [(3,4)(5)], [(3,5)(4)] ]
add (1,2) to each list in result which gives us [ [(1,2)(3,4)(5)], [(1,2)(3,5)(4)] ]

first = (1,3), compute([2,4,5])
    first = (2,4), compute([5])
        return (5)
    result = result + [(2,4)(5)]
    first = (2,5) compute([4])
        return (4)
    result = result + [(2,5),(4)] (a this point result is [ [(2,4)(5)], [(2,5)(4)] ]
add (1,3) to each list in result which gives us [ [(1,3)(2,4)(5)], [(1,3)(2,5)(4)] ]
at this point we have:
[ [(1,2)(3,4)(5)], [(1,2)(3,5)(4)], [(1,3)(2,4)(5)], [(1,3)(2,5)(4)] ]

对 (1,4)、(1,5) 和 (1,6) 继续此操作,您就完成了。您不必从 (2,3) 开始并执行 compute([1,4,5]),因为这会导致重复。

这个算法也应该适用于偶数集。

还没有测试过,没有证明但看起来不错,应该很容易编码,如果它有效,那么它应该很快,因为它只会进行必要的计算。

在代码中(我在这里写了整个代码,所以它完全没有经过测试,可能包含编译和逻辑错误!):

public Set<List<Pair>> compute(List<Integer> numbers) {
    if(numbers.size() < 3) {
            // Base case
        List<Pair> list = new ArrayList<>();
        list.add(new Pair(numbers));
        Set<List<Pair>> result = new HashSet<>();
        result.add(list);
        return result;
    } else {
        Set<List<Pair>> result = new HashSet<ArrayList<>>();
        // We take each pair that contains the 1st element
        for(int i = 1; i < numbers.size(); i++) {
            Pair first = new Pair(numbers.get(0), numbers.get(i));
            // This is the input for next level of recursion
            // Our numbers list w/o the current pair
            List<Integers> nextStep = new ArrayList<>(numbers);
            nextStep.remove(i);
            nextStep.remove(0);
            Set<List<Pair>> intermediate = null;
            if(nextStep.size() % 2 == 0) {
                intermediate = compute(nextStep);
            } else {
                intermediate = compute(numbers).addAll( firstElementSingle(numbers) ),compute( nextStep );
            }
            for(List<Pair> list : intermediate ) {
                // We add the current pair at the beginning
                list.add(0, first);
            }
            result.addAll(intermediate);
        }
        return result;
    }
}

如果我错过了什么,我自己很好奇,所以我期待您的反馈:-)

@编辑:

正如@yusuf 指出的那样,当输入是奇数列表时,这会丢失一些排列。它会遗漏 [1] 为单个元素的所有结果。

但如果我在这种情况下没有记错的话(奇数个元素),这应该可以工作:

compute(numbers).addAll( firstElementSingle(numbers) )

firstElementSingle 在哪里:

private Set<List<Integer>> firstElementSingle(List<Integer> numbers) {
    Set<List<Integer>> result compute(numbers.subList(1,numbers.size()) );
    for(List<Integer> list : result) {
        list.add(numbers.get(0));
    }
    return result;
}

而且仍然只会产生必要的结果。

【讨论】:

  • 你几乎是对的!当我们失去排列时,有一个问题。由于奇异元素始终位于数组的末尾,因此不可能使前半部分元素成为那些奇异元素。例如。对于 1,2,3,4,5,永远不会出现 1 单独存在的情况 ([1])。我会尝试介绍一些有意义的标志
  • 你是对的!但在这种情况下,首先对整个输入数组运行它还不够,例如 (1,2,3,4,5) 这将给出 result1 然后再次为没有第一个元素的数组运行它 (2, 3,4,5) 并将 (1) 添加到该集合的每个列表中,这将给出 result2 并结合 result1 和 result2?它仍然只会生成必要的列表。你只需要一个开关 if(even) compute(numbers) else join(compute(numbers),compute(numbers.withoutFirst))
  • 所以事实上 - 不是真的......我已经指出了 1 是单数元素的情况,但是对于 n=5 我们也能够得到 [2] 单数(其中[3] 独自一人时的一种情况)。我添加了附加标志,用于确定给定路径中是​​否已使用单个对象,但这会使某些结果再次重复。正如您所提到的,删除重复项不是一个好方法。有趣的是,我发现,添加一个数字使计数相等并不会改变结果的计数。因此,将 n 对齐为偶数并最终忽略这个添加的数字可能是一个想法。
  • 嗯,你确定吗?我可以看到为什么 [1] 是单个元素的结果不会出现(即 [(2,3),(4,5),(1)]),例如 [1,2,3,4 ,5] 但我认为我的算法会产生 [2]、[3]、[4]、[5] 是单个元素的所有可能性。你能给我一个仍然被省略的例子吗?
  • 因此在您的示例中很容易看到。看看第一点:“将 (1,2) 添加到结果中的每个列表中,这给了我们 [ [(1,2)(3,4)(5)], [(1,2)(3,5) (4)]]"。使用 (1,2) 我们在这里缺少例如(3) 单独存在的情况 -> (1,2)(3)(4,5)。数字越大(离 1 越远),它出现的频率就越高。例如。 (5) 在每种情况下都单独出现,但 (1) 没有出现。 (您的示例中缺少第二种情况(2))
【解决方案2】:

您将每对处理两次,以更正此替换 if 条件:比较 &lt;&gt; 而不是 !=。 这不会大大提高性能,但至少应该可以按您的预期工作。

【讨论】:

  • 这实际上消除了一些重复,但我仍然面临例如:[0 1] [2 3] vs [2 3] [0 1]
猜你喜欢
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-27
  • 1970-01-01
相关资源
最近更新 更多