查找具有非冲突类别的项目的子集

时间:2015-03-07 15:53:11

标签: algorithm set combinatorics

我有一个对象列表,每个项目都有一个成本和一组与之关联的资源(见下文)。我正在寻找一种基于组合成本从该列表中选择子集的方法,并且每个资源最多必须包含一次(不是每个资源都必须包含在内)。计算子集的组合成本的方式应该是可交换的(例如,max,min,avg)。如果两个子集具有相同的组合成本,则选择具有更多项目的子集。

 Item  |  cost   resources [1..3]
 ================================
  P1   |  0.5          B
  P2   |   4         A B C
  P3   |  1.5        A B
  P4   |   2             C
  P5   |   2         A

这将允许这些组合:

 Variant  |   Items    sum
 ==========================
    V1    |  P1 P4 P5  4.5
    V2    |     P2      4
    V3    |   P3 P4    3.5

对于最大选择,将选择V1。项目数量可以在1到几十之间,对于资源数量也是如此。

我的蛮力方法只是总结所有可能排列的成本并选择最大/最小排列,但我认为有一种更有效的方法来做到这一点。我在Java 8中进行编码,但我对伪代码或Matlab很好。

我发现了一些似乎相似的问题(例如(1)(2)(3)),但我无法将它们转移到我的问题中,所以请原谅我你认为这是重复的:/

提前致谢! 〜

澄清

我的一个朋友对我想要的套装感到困惑。无论我最终如何选择我的子集,我总是希望生成尽可能多的项目的子集。如果我已经将P3添加到我的子集中并且可以添加P4而不会产生冲突(也就是说,资源在子集中使用了两次)那么我想要P3 + P4,而不仅仅是P3。

Clarification2

"变种不必包含所有资源"意味着如果在不产生冲突的情况下添加项目以填充缺少的资源槽(因为缺少资源的所有项目也已经存在另一个资源),则表示子集已完成。

2 个答案:

答案 0 :(得分:0)

这个问题是NP-Hard,即使没有“资源”因素,您也在处理knapsack-problem

如果您可以将成本转换为相对较小的整数,您可以通过为每个分配的资源添加一个维度来修改Knapsack的动态规划解决方案,并且具有类似于的公式(显示概念,确保所有边缘情况如果需要,可以工作或修改):

D(_,_,2,_,_) = D(_,_,_,2,_) = D(_,_,_,_,2) = -Infinity
D(x,_,_,_,_) = -Infinity   x < 0
D(x,0,_,_,_) = 0 //this stop clause is "weaker" than above stop clauses - it can applies only if above don't.
D(x,i,r1,r2,r3) =  max{1+ D(x-cost[i],i-1,r1+res1[i],r2+res2[i],r3+res3[i]) , D(x,i-1,r1,r2,r3)} 

其中cost是成本数组,res1,res2,res3,...是eahc项目所需资源的二进制数组。

复杂性为O(W*n*2^#resources)

答案 1 :(得分:0)

在给我的问题更多想法后,我提出了一个解决方案,我为此感到非常自豪。这个解决方案:

  • 将找到所有可能的完整变体,即不会在不引起冲突的情况下添加其他项目的变体
  • 还会找到一些非完整的变体。我可以忍受。
  • 可以通过任何方式选择最终变体。
  • 使用非整数项值。

我意识到这确实是不是背包问题的一个变种,因为这些项目有一个值,但没有与它们相关的权重(或者,你可以把它解释为多重的变体)尺寸背包问题变量,但所有权重相等)。代码使用了一些lambda表达式,如果你不使用Java 8,你将不得不替换它们。

public class BenefitSelector<T extends IConflicting>
{
public ArrayList<T> select(ArrayList<T> proposals, Function<T, Double> valueFunction)
{
    if (proposals.isEmpty())
        return null;


    ArrayList<ArrayList<T>> variants = findVariants(proposals);

    double value = 0;
    ArrayList<T> selected = null;

    for (ArrayList<T> v : variants)
    {
        double x = 0;

        for (T p : v)
            x += valueFunction.apply(p);

        if (x > value)
        {
            value = x;
            selected = v;
        }
    }

    return selected;
}

private ArrayList<ArrayList<T>> findVariants(ArrayList<T> list)
{
    ArrayList<ArrayList<T>> ret = new ArrayList<>();
    Conflict c = findConflicts(list);

    if (c == null)
        ret.add(list);

    else
    {
        ret.addAll(findVariants(c.v1));
        ret.addAll(findVariants(c.v2));
    }

    return ret;
}

private Conflict findConflicts(ArrayList<T> list)
{
    // Sort conflicts by the number of items remaining in the first list
    TreeSet<Conflict> ret = new TreeSet<>((c1, c2) -> Integer.compare(c1.v1.size(), c2.v1.size()));

    for (T p : list)
    {
        ArrayList<T> conflicting = new ArrayList<>();

        for (T p2 : list)
            if (p != p2 && p.isConflicting(p2))
                conflicting.add(p2);

        // If conflicts are found create subsets by
        // - v1: removing p 
        // - v2: removing all objects offended by p
        if (!conflicting.isEmpty())
        {
            Conflict c = new Conflict(p);

            c.v1.addAll(list);
            c.v1.remove(p);

            c.v2.addAll(list);
            c.v2.removeAll(conflicting);

            ret.add(c);
        }
    }

    // Return only the conflict with the highest number of elements in v1 remaining.
    // The algorithm seems to behave in such a way that it is sufficient to only  
    // descend into this one conflict. As the root list contains all items and we use
    // the remainder of objects there should be no way to miss an item.
    return ret.isEmpty() ? null 
                         : ret.last();
}



private class Conflict
{
    /** contains all items from the superset minus the offending object */
    private final ArrayList<T>  v1  = new ArrayList<>();

    /** contains all items from the superset minus all offended objects */
    private final ArrayList<T>  v2  = new ArrayList<>();

    // Not used right now but useful for debugging
    private final T             offender;


    private Conflict(T offender)
    {
        this.offender = offender;
    }
}
}

使用以下设置的变体进行测试:

public static void main(String[] args)
{
    BenefitSelector<Scavenger> sel = new BenefitSelector<>();

    ArrayList<Scavenger> proposals = new ArrayList<>();
    proposals.add(new Scavenger("P1",   new Resource[] {Resource.B},                            0.5));
    proposals.add(new Scavenger("P2",   new Resource[] {Resource.A, Resource.B, Resource.C},    4));
    proposals.add(new Scavenger("P3",   new Resource[] {Resource.C},                            2));
    proposals.add(new Scavenger("P4",   new Resource[] {Resource.A, Resource.B},                1.5));
    proposals.add(new Scavenger("P5",   new Resource[] {Resource.A},                            2));
    proposals.add(new Scavenger("P6",   new Resource[] {Resource.C, Resource.D},                3));
    proposals.add(new Scavenger("P7",   new Resource[] {Resource.D},                            1));

    ArrayList<Scavenger> result = sel.select(proposals, (p) -> p.value);
    System.out.println(result);
}

private static class Scavenger implements IConflicting
{
    private final String        name;
    private final Resource[]    resources;
    private final double        value;


    private Scavenger(String name, Resource[] resources, double value)
    {
        this.name = name;
        this.resources = resources;
        this.value = value;
    }



    @Override
    public boolean isConflicting(IConflicting other)
    {
        return !Collections.disjoint(Arrays.asList(resources), Arrays.asList(((Scavenger) other).resources));
    }

    @Override
    public String toString()
    {
        return name;
    }
}

这导致[P1(B),P5(A),P6(CD)]的组合值为5.5,高于任何其他组合(例如[P2(ABC),P7(D)] = 5)。由于变体在被选中之前不会丢失,因此处理相同的变体也很容易。