首先,我将粘贴场景,然后提出我的问题:
假设您有一个类别列表,例如:
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
如您所见,每一项至少都属于一个类别,它可能会涉及更多或全部,但最小值始终是一个类别。
例如Cheese
是Food
和Dairy
。
每个项目都有两个属性:
1)价格标签
2)随机值
一个集合定义为每个类别都映射到一个项目。
换句话说,所有类别都必须出现在集合中。
上述项目中的一组可能是:
[Pudding,Lamb,Milk,Apple,Tomato,Legume,Bread,Barley Soup]
如您所见,每个项目都映射到一个类别插槽:
我的问题是,从给定的项目列表中生成上述类别的有序集合的最有效算法是什么?
最好的集合被定义为总共具有最高的随机值。
唯一的限制是,任何生成的集合总计不能超过一定的固定数量,换句话说,所有生成的集合都应在此价格上限之内。
希望我很清楚,谢谢!
答案 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 problem为NP-hard,这意味着没有已知的多项式时间(即O(n ^ k))解。如果输入的随机数始终等于价格并且只有1个类别,则该问题与您将遇到的问题相同。换句话说,您的问题至少与背包问题一样困难,因此您不能期望找到有保证的多项式时间解。
您可以使用嵌套循环轻松地组合生成所有有效集:按类别循环,循环遍历该类别中的项目。早期,您可以通过跳过已选择的项目来提高效率,并在发现价格超出上限后跳过整个项目。将这些结果放在堆中,然后可以按顺序将其吐出。
如果您的问题是希望获得性能更好的产品,那么在我看来,这更像是constraint programming,或更具体地说,是constraint satisfaction问题。我建议您看看用于处理此类问题的技术。
答案 3 :(得分:0)
我只能使用我提出的解决方案来解决这个问题。
我希望找到一个更好的。
简介
根集合被定义为所有项目中最好的集合。
此问题的主要发现如下:
解决方案1:
第一步是生成有效或无效的根最佳集。 为此,我们首先从最高值到最低值对项目列表进行排序。并逐项选择一个项目,并将其放在类别中,直到获得全套为止。
然后,我们执行我喜欢的称为delta sort
或relative sort
的操作。它所做的只是简单地获取每个项目,并尝试从根集中的相应类别中减去该值,这为我们提供了相对于根集中的项目类别的增量或值。如果我们添加包含其他类别的类别,则代码会变得更加复杂,但想法却是相同的-将每个项目的从最低到最高的增量值映射到根集中的项目。
现在我们有了这样的结构,我们可以从根集中按顺序生成子代。从根生成的每个集合都被添加到优先级队列中,在价值方面,更好的集合将成为最高优先级。
生成子级后,我们将删除父级,因为如果它无效,则不再需要它。队列中的项目较少意味着插入时间更长。
最终,我们将获得从队列中取出的有效集合。
这一切都很好而且很花哨,但是房间里的粉红色大象是,我们没有阻止我们的生成算法生成重复集,而这会引起一堆恐怖。首先,如果我们生成10个子代,那么每个子代都是唯一的,因此您可以说每个集合都是唯一的,但是当我们生成大子代时会发生什么呢?我们将生成许多重复项!原因是,因为我们一直想要最好的增量,所以即使第5个孩子的增量比第一个孩子差,但是当我们生成它的孩子时,它将使用最佳增量,它与第一个生成第5个孩子的增量相同儿童。实际上,我们生成了n(n-1)/ 2个重复项,这很不好,而第二个原因之所以不好,是因为现在我们的队列将按相同的顺序膨胀,因此我们将继续执行效率极低的冗余操作并且是不可接受的。
为完全避免重复,我们确保仅从索引生成新集合。父级的索引是项目在增量排序列表中的位置,使用此索引,我们可以使子级从该索引开始开始生成新集合,这样我们将永远无法返回并选择在叔叔中挑选的项目。 我们要做的另一件事是确保一旦更改了项目,就永远不能将其更改为同一类别中的其他项目。 这样,每个父级都有责任生成自己的唯一集贡献。为此,我们可以将集合中的每个位置映射到一个位掩码,并执行和操作以快速判断类别是否存在。
避免重复是此解决方案运行得相当快的唯一方法。 生成包含133个项目的50个顶级有效集合大约需要20秒。一旦我们接近200,它将变得非常缓慢。