购物车最小化算法
Posted
技术标签:
【中文标题】购物车最小化算法【英文标题】:Shopping cart minimization algorithm 【发布时间】:2012-02-12 01:36:13 【问题描述】:我有一个products
的列表,其中包含shops
的列表,它已售出。
'Book A': [ShopA, ShopB, ShopC],
'Book B': [ShopC, ShopD],
'Movie C': [ShopA, ShopB, ShopD, ShopE],
...
(各店价格不同)
每个商店也有运费。这是“按订单”的运费,与我的购物车中有多少商品无关。而且各家店铺也不同。
例如:如果我从 ShopA 购买“Book A”,从 ShopC 购买“Book B”,从 ShopA 购买“Movie C”,结果价格为:Book A price in ShopA
+ Book B price in ShopC
+ Movie C price in ShopA
+ @ 987654327@ + ShopA shipping cost
如果运费为零或按每件商品计算且固定不变,那么我只需按price+shipping
字段对报价列表进行排序,然后从每组中获取第一个结果。
我需要全部购买一次的商品,并找到最低价格和结果集。
我不太擅长优化算法和动态编程,所以我需要一个解决方案,或者只是向正确的方向点头。
【问题讨论】:
您能否估计一下您需要处理的商店和产品数量。目前我提出的算法只适用于极少量的产品,我觉得情况并非如此...... 看在上帝的份上,请执行此操作。这就是我最讨厌亚马逊这样的网站的地方:我不仅不知道运费,而且实际上我不知道他们是否会完全在我到达某个地址之前发货结帐。 【参考方案1】:这个问题是NP Hard。
我们将显示从Hitting Set problem 的减少。
命中集合问题:给定集合S1,S2,...,Sn
和一个数字k
:选择大小为S
的集合k
,这样对于每个Si
,都有一个元素@987654329 @ 在S
中,这样s
在Si
中。 [替代定义:Si
和S
的交集不为空]。
减少:
给定一个hit set的实例,以(S1,...,Sn,k)
的形式创建这个问题的一个实例:
All books cost nothing. In order to buy from each store you pay 1.
The book i is sold by each store denoted in Si, minimal price for this instance is k.
证明:
命中集 -> 这个问题:假设在(S1,...,Sn)
中有一个最小命中集,大小为k
。让这个命中集为S
。通过从S
的每家商店购买,我们可以以k
的成本购买我们所有的书,因为这些书没有成本[在我们的建设中],我们买了所有的书,并且我们完全支付了从商店订购的费用@987654342 @,因此总价格为k
。
This question -> Hitting set:假设该问题的定价为k
。然后,从构建问题,由于书籍不花钱,我们需要在k
不同的商店购买才能获得所有书籍。让这些商店成为S
。从问题的构造来看,S
是(S1,...,Sn)
的命中集
Q.E.D.
结论: 因此,这个问题“并不容易”命中集问题,并且对于这个问题没有已知的多项式解决方案,所以 - 如果你想要最佳解决方案,你最好的镜头可能是指数的,例如 @987654323 @ [检查所有可能性,并返回最小的解决方案]。
【讨论】:
我认为这个答案不正确。也许您不需要从特定商店预订任何产品,因为该商店提供的所有产品在其他商店都更便宜。此外,您的解释中不包括“运费”。【参考方案2】:有了这么少的项目,我有一个解决方案。它是动态的。
我们将迭代处理每个商店。在每一步,我们都会存储当前的最佳价格,我们可以使用该价格覆盖所有项目子集。一开始,它们的价格都是infinity
,除了价格的空子集0
。请注意,所有子集的计数均为 2^Num_products
,但在您的情况下,这些子集只有大约 1000 个。
现在我们如何处理下一个关注店铺:考虑您使用该店铺覆盖所有可能的产品子集(我的意思是该店铺可以实际提供的子集)以及您已经覆盖的所有其他产品观察到,从而提高了覆盖每个子集的最小成本。这一步需要2^Num_products*2^Num_products=4^Num_products
,仍然可以承受大约一百万。您为每家商店都这样做,最后的答案是覆盖所有元素的成本。提议的解决方案的整体复杂度是4^Num_products * num_shops
,大约是 5000 万,这很好。
请注意,这仍然是指数级的,这并不奇怪。感谢您对他的 NP 难的令人难以置信的证明。
编辑在伪代码中添加对该算法的进一步解释:
init:
cost[subset] = infi
cost[] = 0
for shop in shops
new_prices = costs.dup()
for set : subsets
for covered_set : all_subsets(set)
price = covered_set == ? 0 : delivery[shop]
remaining = set
for element : covered_set
if shop do not sale element
break for, choose next covered_set
price += el_price[element]
remaining.remove(element)
price += costs[remaining]
new_prices[set] = min(new_prices[set], price)
costs = new_prices
return costs[all]
请注意,这里我使用集合作为索引 - 这是因为我实际上使用子集的位掩码表示,例如 1101 是包含第一个、第二个和第四个元素的子集。因此所有集合的迭代是for (int i = 0; i < (1 << n); i++)
。
还有一件事:如果你想循环一个子集S
的所有子集,你实际上可以比迭代初始集合的所有子集并检查子集是否是S
的子集更快.如果 S 也用位掩码 bit_mask
表示,则此 for 循环可以完成工作:for(int i = bit_mask; i > 0; i = (i - 1) & bitmask)
。使用这种方法,您可以将算法的复杂性降低到3^Num_products * num_shops
。但是,这有点难以理解,您可能需要手动编写一个示例,以确保我编写的循环实际上循环了 S 的所有子集。关于复杂性 - 请相信我。
EDIT2 编辑了中断条件。还让我详细说明集合remaining
及其计算:正如dmzkrsk
指出的伪代码提到从集合中删除,但实际上您可以分配remaining = set ^ covered_set
(再次位操作)以防使用位掩码来表示子集。
【讨论】:
我还不是很清楚。你能提供一个伪代码或按照一些小书/商店集上的步骤操作吗? 完成,如果您需要关于子集表示的更多解释,请告诉我。 嗯,我实现了它,它似乎工作。我只有两个问题要确定:1)如果set
和covered_set
是位掩码,那么remaining
是否等于set - covered_set
,所以我们不需要remaining.remove
数组逻辑? 2) break for on covered_set
表示我们移动到for covered_set : all_subsets(set)
循环中的下一个covered_set
或放弃该循环并移动到for set : subsets
中的下一个集合?
感谢 cmets。这两个问题都是完全合理的。我会将答案放在我的答案的下一次编辑中。
我目前正在考虑是否可以扩展这种算法/方法以支持“数量”:每个商店只有一些库存书籍,我的购物车看起来像“2x Book A, 3书 C”。有人知道如何解决这个问题吗?【参考方案3】:
我曾经处理过这个确切的问题。除了测试所有可能的商店组合之外,我没有想出任何其他解决方案,但是有一种简单的方法可以过滤掉每种产品中的许多商店。
1.计算每种产品的最低价格(包括运费),我们称之为best_price。
2.在每个产品中,只保留价格(不含运费)
3. 测试所有可能的商店组合以获得最便宜的价格。
【讨论】:
另外,算法的另一个巨大的时间改进:跟踪最佳组合总数。如果当前总和大于目前找到的最佳值,则跳至下一次迭代。这两个优化使我的解决方案适用于合理数量的组合。【参考方案4】:蚁群优化是一个很好的启发式方法。我用它来解决旅行推销员问题。您可以从 google tsp 求解器中找到一个工作示例。它是一个使用蛮力和动态编程解决方案的 javascript 库。当您要计算的城市数量超过当前 20 个城市的限制时,将使用 AOC。我相信您可以使用该库来解决您的问题,并且只需要稍微重写一下。有 20 个城市,该程序必须检查 20 个!可能性。在你的情况下,它有点轻,但可能只有一个量级。
【讨论】:
以上是关于购物车最小化算法的主要内容,如果未能解决你的问题,请参考以下文章