生成有序约束集

时间:2018-12-27 04:12:35

标签: algorithm set abstract

首先,我将粘贴场景,然后提出我的问题:

假设您有一个类别列表,例如:

Food,Meat,Dairy,Fruit,Vegetable,Grain,Wheat,Barley

现在,您有了一个适合上述一个或多个类别的项目列表。

以下是项目的示例列表: Pudding,Cheese,Milk,Chicken,Barley,Bread,Couscous,Fish,Apple,Tomato, Banana,Grape,Lamb,Roast,Honey,Potato,Rice,Beans,Legume,Barley Soup

如您所见,每一项至少都属于一个类别,它可能会涉及更多或全部,但最小值始终是一个类别。

例如CheeseFoodDairy

每个项目都有两个属性:
1)价格标签
2)随机值

一个集合定义为每个类别都映射到一个项目。
换句话说,所有类别都必须出现在集合中。

上述项目中的一组可能是:

[Pudding,Lamb,Milk,Apple,Tomato,Legume,Bread,Barley Soup]

如您所见,每个项目都映射到一个类别插槽:

  • 布丁已映射到食品类别
  • 羊肉映射到肉类
  • 牛奶已映射到乳制品类别
  • 苹果已映射到​​水果类别
  • 西红柿被映射到蔬菜类别
  • 豆类被映射到谷物类别
  • 面包被映射到小麦类别
  • 大麦汤映射到大麦类别

我的问题是,从给定的项目列表中生成上述类别的有序集合的最有效算法是什么?

最好的集合被定义为总共具有最高的随机值。

唯一的限制是,任何生成的集合总计不能超过一定的固定数量,换句话说,所有生成的集合都应在此价格上限之内。

希望我很清楚,谢谢!

4 个答案:

答案 0 :(得分:3)

您要实现的是一种最大匹配形式,我不知道是否有一种有效的方法来计算有序集合,但是这种减少仍然可以为您提供帮助。

定义一个二部图,一侧每个类别一个节点,而另一侧每个项目一个节点。如果项目属于该类别,则在该项目和该类别之间添加一条边,权重由该项目的随机值定义。

您定义的“集合”是该图中的最大基数匹配。 如Takeaki Uno所证明的那样,可以在合理的时间内枚举它们。 “ A Fast Algorithm for Enumerating Non-Bipartite Maximal Matchings”,并且由于您的图形是二部图,因此在您遇到的情况下它甚至可能更快。

在这些组合中,您正在寻找权重最大且价格受限制的组合。根据您的数据,仅枚举所有数据,根据价格对其进行过滤,然后对剩余的结果进行排序就足够了。

如果不是这种情况,则可以通过解决组合优化问题来找到最佳集合,该组合优化问题的目标函数是总权重,其约束是价格限制和基数(在交易中称为最大加权匹配)文学)。以这种形式编写问题后,甚至可能已经有在线的求解器。但是,这只会提供一个这样的集合,而不是排序列表,但是由于在一般情况下此问题很难解决,因此这是您所希望的。您可能需要对数据进行更多的假设才能获得更好的结果(例如,此类集合的最大数量,可以属于k个以上类别的最大项目数量的界限,等等)

答案 1 :(得分:1)

好的,这是我第二次尝试回答这个问题。

可以说我们有以下输入内容

class Item {
public:
    // using single unsigned int bitwise check sets
    unsigned int category;
    int name;
    int value;
    int price;
...
};

class ItemSet
{
public:
    set<Item> items;
    int sum;
};

首先,根据最高随机值,然后根据最低价格对输入数据进行排序

bool operator<(const Item& item1, const Item& item2) {
    if (item1.value == item2.value) {
        if (item1.price == item2.price) {
            return item1.name < item2.name;
        }
        return item1.price > item2.price;
    }
    return item1.value < item2.value;
}
...
vector<Item> v = generateTestItem();
sort(v.begin(), v.end(), greater<Item>());

接下来使用回溯将顶级集合收集到堆中,直到满足条件为止。在我们的回溯算法中对输入数据进行排序后,可以确保收集到的数据基于最高value和最低price而排名靠前。需要注意的另一件事是,我将项目类别(currentCats)与位操作进行了比较,这带来了O(1)的复杂性。

priority_queue<ItemSet> output;
void helper(vector<Item>& input, set<Item>& currentItems, unsigned int currentCats, int sum, int index)
{
    if (index == input.size()) {
        // if index reached end of input, exit
        addOutput(currentItems);
        return;
    }
    if (output.size() >= TOP_X) {
        // if output data size reached required size, exit
        return;
    }
    if (sum + input[index].price < PRICE_TAG) {
        if ((input[index].category & currentCats) == 0) {
            // this category does not exists in currentCats
            currentItems.insert(input[index]);
            helper(input, currentItems, currentCats | input[index].category, sum + input[index].price, index + 1);
        }
    } else { 
        addOutput(currentItems);
        return;
    }
    if (currentItems.find(input[index]) != currentItems.end()) {
        currentItems.erase(input[index]);
    }
    helper(input, currentItems, currentCats, sum, index + 1);
    return;
}

void getTopItems(vector<Item>& items)
{
    set<Item> myset;
    helper(items, myset, 0, 0, 0);
}

在最坏的情况下,这种回溯将导致O(2 ^ N)复杂性,但是由于TOP_X是有限值,因此实际花费的时间不会太长。

我试图通过生成随机值来测试此代码,看来工作正常。可以找到完整的代码here

答案 2 :(得分:0)

我不确定“生成有序集”是什么意思。

我认为任何算法都将生成集合,对其进行评分,然后尝试生成更好的集合。考虑到所有限制条件,我认为您不能一口气就能高效生成最佳集合。

已证明0-1 knapsack problemNP-hard,这意味着没有已知的多项式时间(即O(n ^ k))解。如果输入的随机数始终等于价格并且只有1个类别,则该问题与您将遇到的问题相同。换句话说,您的问题至少与背包问题一样困难,因此您不能期望找到有保证的多项式时间解。

您可以使用嵌套循环轻松地组合生成所有有效集:按类别循环,循环遍历该类别中的项目。早期,您可以通过跳过已选择的项目来提高效率,并在发现价格超出上限后跳过整个项目。将这些结果放在堆中,然后可以按顺序将其吐出。

如果您的问题是希望获得性能更好的产品,那么在我看来,这更像是constraint programming,或更具体地说,是constraint satisfaction问题。我建议您看看用于处理此类问题的技术。

答案 3 :(得分:0)


我只能使用我提出的解决方案来解决这个问题。

我希望找到一个更好的。

简介

根集合被定义为所有项目中最好的集合。

此问题的主要发现如下:

  1. 如果我们替换根集中的2个项目,则替换1个项目并不意味着它将自动成为更好的集合。因为即使只有1个,我们也可以替换一个非常坏的项目,但是我们用于替换根集中的2个项目可能会产生更好的集合。同样适用于任何数量的项目,即在某些情况下4次替换仍然比仅1次替换更好。
  2. 我们还必须了解在两种解决方案中不生成重复集的效率。

解决方案1:

第一步是生成有效或无效的根最佳集。 为此,我们首先从最高值到最低值对项目列表进行排序。并逐项选择一个项目,并将其放在类别中,直到获得全套为止。

然后,我们执行我喜欢的称为delta sortrelative sort的操作。它所做的只是简单地获取每个项目,并尝试从根集中的相应类别中减去该值,这为我们提供了相对于根集中的项目类别的增量或值。如果我们添加包含其他类别的类别,则代码会变得更加复杂,但想法却是相同的-将每个项目的从最低到最高的增量值映射到根集中的项目。

现在我们有了这样的结构,我们可以从根集中按顺序生成子代。从根生成的每个集合都被添加到优先级队列中,在价值方面,更好的集合将成为最高优先级。

生成子级后,我们将删除父级,因为如果它无效,则不再需要它。队列中的项目较少意味着插入时间更长。

最终,我们将获得从队列中取出的有效集合。

这一切都很好而且很花哨,但是房间里的粉红色大象是,我们没有阻止我们的生成算法生成重复集,而这会引起一堆恐怖。首先,如果我们生成10个子代,那么每个子代都是唯一的,因此您可以说每个集合都是唯一的,但是当我们生成大子代时会发生什么呢?我们将生成许多重复项!原因是,因为我们一直想要最好的增量,所以即使第5个孩子的增量比第一个孩子差,但是当我们生成它的孩子时,它将使用最佳增量,它与第一个生成第5个孩子的增量相同儿童。实际上,我们生成了n(n-1)/ 2个重复项,这很不好,而第二个原因之所以不好,是因为现在我们的队列将按相同的顺序膨胀,因此我们将继续执行效率极低的冗余操作并且是不可接受的。

为完全避免重复,我们确保仅从索引生成新集合。父级的索引是项目在增量排序列表中的位置,使用此索引,我们可以使子级从该索引开始开始生成新集合,这样我们将永远无法返回并选择在叔叔中挑选的项目。 我们要做的另一件事是确保一旦更改了项目,就永远不能将其更改为同一类别中的其他项目。 这样,每个父级都有责任生成自己的唯一集贡献。为此,我们可以将集合中的每个位置映射到一个位掩码,并执行和操作以快速判断类别是否存在。

避免重复是此解决方案运行得相当快的唯一方法。 生成包含133个项目的50个顶级有效集合大约需要20秒。一旦我们接近200,它将变得非常缓慢。