以仿射成本优化笛卡尔请求

时间:2009-09-10 07:58:18

标签: algorithm language-agnostic optimization combinatorics cost-based-optimizer

我有一个成本优化请求,我不知道如何有文献。这有点难以解释,所以我提前为问题的长度道歉。

我正在访问的服务器以这种方式工作:

  • 对记录(r1,... rn)和字段(f1,... fp)发出请求
  • 您只能申请笛卡尔积(r1,...,rp)x(f1,... fp)
  • 与此类请求相关的费用(时间和金钱)与请求的大小相关:

T((r1, ..., rn)x(f1, ..., fp) = a + b * n * p

不失一般性(仅通过规范化),我们可以假设b=1所以成本是:

T((r1, ...,rn)x(f1,...fp)) = a + n * p

  • 我只需要请求一对(r1, f(r1)), ... (rk, f(rk))的子集,这是一个来自用户的请求。我的程序充当用户和服务器(外部)之间的中间人。我有很多这样的请求(每天数万个)。

从图形上看,我们可以将其视为一个n x p稀疏矩阵,我想用矩形子矩阵覆盖非零值:

   r1 r2 r3 ... rp
   ------      ___
f1 |x  x|      |x|
f2 |x   |      ---
   ------
f3
..    ______
fn    |x  x|
      ------

有:

  • 由于成本不变而使子矩阵的数量保持合理
  • 所有'x'必须位于子矩阵内
  • 由于线性成本,所涵盖的总面积不得太大

我将g命名为我的问题的稀疏系数(所需对的数量超过总可能对​​,g = k / (n * p)。我知道系数a

有一些明显的观察结果:

  • 如果a很小,最好的解决方案是独立请求每个(记录,字段)对,总费用为:k * (a + 1) = g * n * p * (a + 1)
  • 如果a很大,最好的解决方案是请求整个笛卡尔积,总费用为:a + n * p
  • 只要g > g_min = 1/ (a+1) * (1 + 1 / (n * p))
  • ,第二种解决方案就会更好
  • 当然笛卡尔积中的订单并不重要,所以我可以调换矩阵的行和列,使其更容易被覆盖,例如:
   f1 f2 f3
r1  x    x
r2     x 
r3  x    x

可以重新排序为

   f1 f3 f2
r1  x  x
r3  x  x
r2       x

有一个最佳解决方案是请求(f1,f3) x (r1,r3) + (f2) x (r2)

  • 尝试所有解决方案并寻找更低的成本不是一种选择,因为组合学爆炸:
for each permutation on rows: (n!)
   for each permutation on columns: (p!)
       for each possible covering of the n x p matrix: (time unknown, but large...)
           compute cost of the covering

所以我正在寻找一个近似的解决方案。我已经有了某种贪婪算法,它找到了一个给定矩阵的覆盖(它以单元格开始,如果合并中空单元格的比例低于某个阈值,则合并它们。)

为了记住一些数字,我的n介于1到1000之间,而我的介于1到200之间。覆盖模式实际上是“块状的”,因为记录来自于所要求的字段类似的类。不幸的是我无法访问记录类......

问题1 :有人有想法,聪明的简化,还是对可能有用的论文的参考?由于我有很多请求,一个平均平均的算法是我正在寻找的(但我无法承受它在某些极端情况下工作得很差,例如请求整个当n和p很大时,矩阵,请求确实很稀疏。)

问题2 :事实上,问题更复杂:实际上成本更像是形式:a + n * (p^b) + c * n' * p',其中b是常数< 1(一旦记录被要求提供字段,要求其他字段的成本并不太高)并且n' * p' = n * p * (1 - g)是我不想要求的单元格数量(因为它们无效,并且有一个请求无效事物的额外费用)。我甚至不能梦想快速解决这个问题,但仍然......任何人的想法?

6 个答案:

答案 0 :(得分:5)

选择子矩阵以覆盖请求的值是set covering problem的一种形式,因此NP完成。您的问题增加了这个已经很难解决的问题,即集合的成本不同。

您允许排列行和列不是一个大问题,因为您可以只考虑断开连接的子矩阵。第一行,第四列到第七行,第五列,第四列,第二列是有效集,因为您可以只交换第二行和第五行并获得连接的子矩阵行一,第四列到第二行,第七列。当然这会增加一些限制 - 并非所有集合在所有排列下都是有效的 - 但我不认为这是最大的问题。

维基百科的文章给出了不可近似性的结果,即在多项式时间内不能用因子0.5 * log2(n)来解决问题,其中n是集合的数量。在你的情况下,2^(n * p)是一个(相当悲观)的上限集合和数量,你只能在多项式时间内找到一个因子0.5 * n * p的解决方案(除了N = NP并忽略)不同的成本)。

忽略行和列排列的集合数的乐观下限为0.5 * n^2 * p^2,产生更好的log2(n) + log2(p) - 0.5因子。因此,您只能期望在最坏情况下找到一个解决方案n = 1000p = 200,在乐观情况下最多约为17,最多可达{{{1}。 1}}在悲观情况下(仍然忽略不同的成本)。

因此,您可以做的最好的事情是使用启发式算法(维基百科文章提到了几乎最优的贪婪算法),并接受算法执行(非常)坏的情况。或者你走另一条路并使用优化算法,并尝试找到一个好的解决方案,使用更多的时间。在这种情况下,我建议尝试使用A* search

答案 1 :(得分:1)

我确信在某个地方有一个非常好的算法,但这是我自己的直观想法:

  1. 抛出一些矩形的方法:

    • 根据 a 确定“大致最佳”的矩形尺寸。
    • 将这些矩形(可能是随机的)放在所需的点上,直到覆盖所有点。
    • 现在拍摄每个矩形并尽可能缩小它,而不会“丢失”任何数据点。
    • 找到彼此接近的矩形,并决定将它们组合起来是否比将它们分开要便宜。
  2. 生长

    • 从自己的1x1矩形中的每个点开始。
    • 找到n行/列内的所有矩形(其中n可能基于 a );看看你是否可以将它们组合成一个矩形而不需要任何费用(或负成本:D)。
    • 重复。
  3. 收缩

    • 从一个覆盖所有点的大矩形开始。
    • 寻找一个与大一边共用一对边的子矩形,但只包含很少的点。
    • 将它从大的那个中剪下来,产生两个较小的矩形。
    • 重复。
    • 将飞机划分为4个矩形。对于其中的每一个,请查看是否通过进一步递归或仅包含整个矩形来获得更好的成本。
    • 现在拿走你的矩形,看看你是否可以很少/没有成本合并它们。\
  4. 另外:请记住有时最好有两个重叠矩形,而不是一个大矩形,这是它们的超集。例如。两个矩形在一个角落重叠的情况。

答案 2 :(得分:1)

好的,我对这个问题的理解已经改变了。新想法:

  • 将每一行存储为长位字符串。和位串对,试图找到最大化1位数的对。将这些对成长为更大的组(排序并尝试将真正的大对彼此匹配)。然后构造一个将命中最大组的请求,然后忘记所有这些位。重复完成所有操作。也许有时会从行切换到列。

  • 查找其中包含零点或几点的所有行/列。暂时“删除”它们。现在,您正在查看将其排除的请求所涵盖的内容。现在也许应用其他技术之一,然后处理被忽略的行/列。另一种思考方式是:先处理更密集的点,然后再使用更稀疏的点。

答案 3 :(得分:0)

我认为用户请求中提到的n个记录(行)和p字段(cols)设置为p维空间({0,1} ^ p)中的n个点,第i个坐标为1 iff it有一个X和identify a hierarchy of clusters,根目录中包含所有X的最粗集群。对于集群层次结构中的每个节点,请考虑覆盖所有所需列的产品(这是行(任何子节点)x cols (任何子节点))。然后,自下而上决定是否合并子覆盖物(支付整个覆盖物),或将它们作为单独的请求保留。 (覆盖物不是连续的列,而是完全需要的那些;即考虑一点矢量)

我同意Artelius的意见,即重复的产品要求可能更便宜;我的等级方法需要改进才能纳入。

答案 4 :(得分:0)

由于您的值很稀疏,是否有许多用户要求类似的值?是否在您的应用程序中缓存?请求可以通过作为(x,y)位置函数的散列索引,以便您可以轻松识别位于网格正确区域内的缓存集。例如,将缓存的集存储在树中将允许您快速找到覆盖请求范围的最小缓存子集。然后,您可以对子集执行线性查找,这很小。

答案 5 :(得分:0)

我已经对它做了一些工作,这是一个明显的,O(n ^ 3)贪婪,对称破解算法(记录和字段被单独处理)在类似python的伪代码中。

这个想法很简单:我们首先尝试每个记录一个请求,然后我们做最有价值的合并,直到没有什么值得合并。这个算法有明显的缺点,它不允许重叠请求,但我希望它在现实生活中很好地工作(使用a + n * (p^b) + c * n * p * (1 - g)成本函数):

# given are
# a function cost request -> positive real
# a merge function that takes two pairs of sets (f1, r1) and (f2, r2) 
# and returns ((f1 U f2), (r1 U r2))

# initialize with a request per record

requests = [({record},{field if (record, field) is needed}) for all needed records]
costs = [cost(request) for request in requests]

finished = False

while not finished: # there might be something to gain
    maximum_gain = 0
    finished = True
    this_step_merge = empty

    # loop onto all pairs of request
    for all (request1, request2) in (requests x request) such as request1 != request2:
        merged_request = merge(request1, request2)
        gain = cost(request1) + cost(request2) - cost(merged_request)

        if gain > maximum_gain:
            maximum_gain = gain
            this_step_merge = (request1, request2, merged_request)

    # if we found at least something to merge, we should continue
    if maximum_gain > 0:
        # so update the list of requests...
        request1, request2, merged_request = this_step_merge
        delete request1 from requests
        delete request2 from requests
        # ... and we are not done yet
        insert merged_request into requests
        finished = False

output requests

这是O(n3 * p)因为:

  • 初始化后,我们从n次请求
  • 开始
  • while循环在每次迭代时从池中删除一个请求。
  • 内部for循环迭代(ni^2 - ni)/ 2个不同的请求对,ni在最坏的情况下从n变为1(当我们将所有内容合并为一个大的时候)请求)。

    1. 有人可以帮我指出算法的非常糟糕的情况。使用这个是否合理?
    2. O(n ^ 3)对于大输入来说太昂贵了。有什么想法来优化它吗?

提前致谢!