2

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

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

4

2 回答 2

2

你可以做的两件事:

1)坏事:

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

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

b) 保持 allPairs 列表排序

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

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

我将在这里做两个假设:

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

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

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

-> 对于列表“数字”中包含第一个元素的每一对数字(即在数字 = [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) 开始并执行 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;
}

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

于 2013-10-19T16:13:39.097 回答
0

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

于 2013-10-19T15:01:24.173 回答