所有可能的对

时间:2013-10-19 13:36:06

标签: java math combinations probability

给定一组数字,从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]

所以:我应该如何实现问题以摆脱重复。如何改进实施以加快处理速度?

2 个答案:

答案 0 :(得分:2)

你可以做两件事:

1)坏事:

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

a)确保每对总是处于某种顺序,例如总是[smallerNumber, biggerNumber]

b)保持allPairs列表排序

然后,当你完成计算时,删除重复项将是微不足道的。这是一个糟糕的方法,因为它会很慢。

2)您可以使用其他算法:

我将在这里作出两个假设:

  • 这是一套,所以我们没有重复
  • 它们可以很容易地分类,即1到5我们知道它将是1,2,3,4,5(但它也适用于1,3,5,6的情况)

基本上我们会有一个递归算法(和你的一样)。输入将是“数字” - &gt;有序(升序)列表,输出将是一组对的列表。让我们再次调用此方法compute(列表编号)。

- &GT;输入列表“数字”具有1或2个元素时的基本情况。在这种情况下,返回一个包含一个列表的集合,该列表包含一个“对”,其中包含1个元素或两个元素。即数字= [2,3]或数字= [2]然后返回一个包含一个列表的集合,其中包含一个“对”(2,3)或(3)。

- &GT;对于包含第一个元素的列表“数字”中的每对数字(即在数字递归的第一级= [1,2,3,4,5],它将是[1,2],[1,3] ],[1,4],[1,5],[1,6])致电Set<List<Pair> 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)开头并执行计算([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;
    }
}

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

<强> @EDIT:

正如@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 :(得分:0)

您正在处理每对两次,要更正此项,请更换if条件:比较<>而不是!=。 这不会提高性能,但至少应该按预期工作。