在满足约束的同时将球放入箱中

时间:2017-06-17 10:00:41

标签: java algorithm combinatorics brute-force

我的问题可以简化为以下几点:

我有11整数(球)(编号从1到11)和4方框(箱子)(方框A, B, C, D)。我想将11整数的分布数量计入满足以下约束的4框中:

  • A的容量为1(一次只能容纳1个整数)
  • B, C, D不能包含总计最多13个
  • 的2个整数
  • 整数只能放在它喜欢的方框中。

我所拥有的是11个整数及其所喜欢的框的列表(表示为x->A,B,C,D,可以读作"整数x喜欢框A,B,C,D& #34):

1->(A, D)2->(B)3->(B)4->(B)5->(B)6->(C)7->(C,D)8->(C,D)9->(C,D)10->(C,D)11->(D)

在这种情况下,解决方案是8,有8种方法可以在框之间分配整数:

A = [1],B = [2,3,4,5],C = [6,8,9,10],D = [7,11]和另外7个分布,其中8,9,10在方框C和D之间分配不同。

现在在纸上做这件事很好,但我正在努力寻找一种系统的方式(蛮力)来完成满足约束的所有可能的分布。我的方法包括11个for循环(每个数字一个),每个循环遍历一个列表(每个数字都有一个填充了它喜欢的框的列表)。我在计算分配数量的同时试图强制执行约束时迷失了,是否有一种更优雅,更通用的方法将这些球解决到容器中,同时满足一些约束问题?

2 个答案:

答案 0 :(得分:2)

(1)定义Box类。

copy.deepcopy()

(2)定义实际框。

abstract class Box {

    Set<Integer> balls = new HashSet<>();
    abstract boolean canAdd(int n);

    boolean add(int n) {
        if (!canAdd(n)) return false;
        balls.add(n);
        return true;
    }

    void remove(int n) { balls.remove(n); }
    @Override public String toString() { return balls.toString(); }
}

class Box1 extends Box {
    // has a capacity of 1 (only 1 integer fits into it at one time)
    boolean canAdd(int n) {
        return balls.size() < 1;
    }
}

class Box13 extends Box {
    // cannot contain 2 integers which sum up to 13
    boolean canAdd(int n) {
        int remain = 13 - n;
        return !balls.contains(remain);
    }
}

(3)定义整数的首选项。

Box A = new Box1();
Box B = new Box13();
Box C = new Box13();
Box D = new Box13();

(5)找到解决方案。

Box[][] likes =  {
    /*  0 */ {},
    /*  1 */ {A, D},
    /*  2 */ {B},
    /*  3 */ {B},
    /*  4 */ {B},
    /*  5 */ {B},
    /*  6 */ {C},
    /*  7 */ {C, D},
    /*  8 */ {C, D},
    /*  9 */ {C, D},
    /* 10 */ {C, D},
    /* 11 */ {D},
};

RESULT

for (Box b1 : likes[1]) {
    if (!b1.add(1)) continue;
    for (Box b2 : likes[2]) {
        if (!b2.add(2)) continue;
        for (Box b3 : likes[3]) {
            if (!b3.add(3)) continue;
            for (Box b4 : likes[4]) {
                if (!b4.add(4)) continue;
                for (Box b5 : likes[5]) {
                    if (!b5.add(5)) continue;
                    for (Box b6 : likes[6]) {
                        if (!b6.add(6)) continue;
                        for (Box b7 : likes[7]) {
                            if (!b7.add(7)) continue;
                            for (Box b8 : likes[8]) {
                                if (!b8.add(8)) continue;
                                for (Box b9 : likes[9]) {
                                    if (!b9.add(9)) continue;
                                    for (Box b10 : likes[10]) {
                                        if (!b10.add(10)) continue;
                                        for (Box b11 : likes[11]) {
                                            if (!b11.add(11)) continue;
                                            System.out.printf("A=%s B=%s C=%s D=%s%n",
                                                A, B, C, D);
                                            b11.remove(11);
                                        }
                                        b10.remove(10);
                                    }
                                    b9.remove(9);
                                }
                                b8.remove(8);
                            }
                            b7.remove(7);
                        }
                        b6.remove(6);
                    }
                    b5.remove(5);
                }
                b4.remove(4);
            }
            b3.remove(3);
        }
        b2.remove(2);
    }
    b1.remove(1);
}

递归求解器是

A=[1] B=[2, 3, 4, 5] C=[6, 8, 9, 10] D=[7, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8, 9] D=[7, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8, 10] D=[7, 9, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8] D=[7, 9, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 9, 10] D=[7, 8, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 9] D=[7, 8, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 10] D=[7, 8, 9, 11]
A=[1] B=[2, 3, 4, 5] C=[6] D=[7, 8, 9, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 9, 10] D=[1, 7, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 9] D=[1, 7, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 10] D=[1, 7, 9, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8] D=[1, 7, 9, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 9, 10] D=[1, 7, 8, 11]
A=[] B=[2, 3, 4, 5] C=[6, 9] D=[1, 7, 8, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 10] D=[1, 7, 8, 9, 11]
A=[] B=[2, 3, 4, 5] C=[6] D=[1, 7, 8, 9, 10, 11]

static void solve(Box A, Box B, Box C, Box D, Box[][] likes, int n) {
    if (n > 11) {
        System.out.printf("A=%s B=%s C=%s D=%s%n", A, B, C, D);
        return;
    }
    for (Box box : likes[n]) {
        if (!box.add(n)) continue;
        solve(A, B, C, D, likes, n + 1);
        box.remove(n);
    }
}

答案 1 :(得分:1)

你的方法听起来不错。

选择一种方法来表示将球分配到方框中(例如,数组,以球为索引,方框为值)。

编写一个函数isDistributionValid(distribution),它接受​​一个分布,如果满足所有约束,则返回true。您可能想要编写以下辅助函数:

  • getBallsInBox(distribution, box):返回一个列表,其中包含分配给box的所有球
  • count(distribution, box):返回分配中box分配的球数
  • anyPairSumsToTarget(list, target):如果true中的任何一对值总和为list,则返回target(提示:使用两个嵌套的for循环)

在11个循环的最里面,使用当前分布的表示来调用isDistributionValid。如果结果为true,则递增计数器。

递归解决方案

使用递归有一种更优雅的方式。它具有较少的重复代码,并且不必修复球的数量。想法是写一个函数

count_distributions(current_ball, last_ball, distribution)

调用该函数时,current_ball之前的所有球应该已经分配到框中,并且分配应该存储在distribution中。该函数返回通过将剩余的球分配给框可以生成的有效分布数。为此,该函数迭代current_ball的可能框,并且对于每种可能性,它递归调用count_distributions(current_ball+1, last_ball, new_distribution)并对结果求和。作为基本情况,如果current_ball大于last_ball,则分发完成,函数应检查分布是否有效,如果是,则返回1,否则返回0。该功能使用count_distributions(1, 11, empty_distribution)启动。

伪代码:

function count_distributions(current_ball, last_ball, distribution):
    if current_ball > last_ball:
        // all the balls have been distributed
        if distribution is valid:
            return 1
        else
            return 0
    sum = 0
    // try each possibility for current_ball
    for chosen_box in boxes that current_ball likes:
        new_distribution = copy(distribution)
        update new_distribution so current_ball maps to chosen_box
        // recursively try remaining balls
        sum += count_distributions(current_ball + 1, last_ball, new_distribution)
    return sum

print(count_distributions(1, 11, empty distribution))

优化

如果当前不完整的分发无效,则可以通过提前终止来使两个版本明显更快。每次将(球,盒)分配添加到分配时,请检查不完整分布是否有效。如果不是,则继续进行分发是没有意义的。

通过向分布表示添加更多信息,可以更有效地检查约束,但我不会详细介绍。