【问题标题】:How to solve this linear programing problem?如何解决这个线性规划问题?
【发布时间】:2011-07-27 09:08:27
【问题描述】:

我不太擅长线性规划,所以我在这里发布了这个问题。 希望有人能指出我正确的方向。 这不是作业问题,所以不要误会。

我有一个 5x5 (25 个节点) 的矩阵。每个节点与其相邻节点(或邻居节点)之间的距离为1 个单位。一个节点可以处于 2 个条件之一:cache 或 access。如果节点 'i' 是缓存节点,访问节点 'j' 可以访问它,代价为 Dij x Aij (访问成本)。 Dij 是节点 i 和 j 之间的曼哈顿距离。 Aij 是节点 i 到 j 的访问频率。

为了成为一个缓存节点i,它需要从一个现有的缓存节点k进行缓存,成本为Dik x C,其中C是一个整数常量。 (缓存成本)。 C称为缓存频率。

A 以 25x25 矩阵的形式提供,其中包含显示任意节点 i 和 j 之间访问频率的所有整数。 D 以 25x25 矩阵的形式提供,其中包含任意一对节点 i 和 j 之间的所有曼哈顿距离。

假设矩阵中有1个缓存节点,找出其他缓存节点和访问节点的集合,使总成本最小化。 总成本 = 总缓存成本 + 总访问成本 .

【问题讨论】:

  • 我希望我被要求解决这个问题,我很想回顾一下我的线性规划。唉,我生疏了,没有机会了。 :(
  • 我确实解决了这个问题。但这是详尽的方法。这需要很长时间。这就是为什么我想知道比我更聪明的人是否可以更快地解决它,我敢打赌:)
  • 您是否尝试过为此查看最小切算法?你确定只有LP才能解决?从外观上看,它似乎在“尖叫”最小切(我可能很难听:)。从我的脑海中:您在节点之间有边缘成本,并且希望将集合分成两部分(缓存节点和访问节点)以最小化成本......我会花一些时间将其减少到最小切割(或更多特别是图像分割),看看它是否可以工作
  • 这似乎是子集问题的一种变体,即 NP-Complete。我不确定它是否完全等同于 NP-Complete 问题,但它很可能是 NP-Complete。这意味着您正在查看指数运行时间以全面搜索解决方案空间。 NP-Complete 问题很难准确解决/非常耗时,但大约需要解决。可以很快找到解决方案。最近我读到“蜂巢”算法可以非常成功地解决 NP-Complete 问题并得出合理的解决方案。

标签: algorithm caching tree matrix


【解决方案1】:

解是一个集合,所以这不是一个线性规划问题。它是连接设施位置的一个特例。 Bardossy 和 Raghavan 的启发式方法看起来很有希望:http://terpconnect.umd.edu/~raghavan/preprints/confl.pdf

【讨论】:

  • 事实上;这是一个内部编程问题。通常很难解决
  • 其实是一个二进制编程问题。使用Simplex求解作为上界的线性程序,剪枝候选空间,有效求解整数和二进制程序。
【解决方案2】:

螺旋缓存是解决方案的类比吗? http://strumpen.net/ibm-rc24767.pdf

【讨论】:

    【解决方案3】:

    我已经解决了一些类似的问题。

    首先,如果您不需要确切的答案,我通常建议您研究诸如遗传算法之类的东西,或者做一个贪心算法。这不会是正确的,但通常也不会很糟糕。它会比精确算法快得多。例如,您可以从所有点开始作为缓存点,然后找到最能降低成本的点,使其成为非缓存点。继续直到移除下一个导致成本上升,并将其用作您的解决方案。这不会是最好的。它通常会相当不错。

    如果您确实需要一个准确的答案,您将需要对大量数据进行暴力搜索。假设指定了初始缓存点,您将有 224 = 16,777,216 个可能的缓存点集进行搜索。太贵了。

    更便宜(注意,不是便宜,只是更便宜)的诀窍是找到修剪搜索的方法。记住这样一个事实,如果在你查看的每个集合上做 100 倍的工作,让你从考虑中平均删除 10 个点作为缓存点,那么你的整体算法将访问 0.1% 的集合,并且你的代码将运行快 10 倍。因此,即使修剪步骤相当昂贵,也值得在早期且经常性地进行修剪。

    您通常需要多种修剪策略。其中之一通常是“我们可以从这里做的最好的事情比我们以前发现的最好的事情还差”。如果您已经找到了一个非常好的最佳解决方案,这会更好。因此,在寻找解决方案的过程中进行一些局部优化通常是值得的。

    通常,这些优化不会改变您正在做大量工作的事实。但它们确实让您的工作量减少了几个数量级。

    我最初的尝试将利用以下观察结果。

    1. 假设x 是一个缓存点,y 是它最近的缓存邻居。然后,如果您只是将缓存更新流量从x 路由到y 沿着该路径,那么您始终可以“免费”创建从x 到y 缓存的路径。因此,不失一般性,这组缓存点连接在网格上。
    2. 如果最终的最低成本可能超过我们发现的当前最佳成本,那么我们还没有走向全球解决方案。
    3. 一旦距离缓存点大于 1 的所有点的访问率加上您仍然可以使用的缓存点邻居的最高访问频率之和小于缓存频率,添加更多缓存点总是会损失的。 (这将是“让我们提前 10 分钟停下来的昂贵条件。”)
    4. 当前缓存点集的最高访问邻居是下一个缓存点尝试的合理候选者。 (您可以尝试其他几种启发式方法,但这种方法是合理的。)
    5. 任何总访问频率超过缓存频率的点都绝对是缓存点。

    这可能不是最好的观察结果。但这可能是相当合理的。要利用这一点,您至少需要一种您可能不熟悉的数据结构。如果您不知道priority queue 是什么,那么请使用您选择的语言四处寻找一个高效的。如果找不到,heap 很容易实现,并且作为优先级队列工作得很好。

    考虑到这一点,假设您已经获得了您所描述的信息和一个初始缓存节点P,这里是一个用于找到最佳算法的伪代码。

    # Data structures to be dynamically maintained:
    #  AT[x, n] - how many accesses x needs that currently need to go distance n.
    #  D[x] - The distance from x to the nearest cache node.
    #  CA[x] - Boolean yes/no for whether x is a cache node.
    #  B[x] - Boolean yes/no for whether x is blocked from being a cache node.
    #  cost - Current cost
    #  distant_accesses - The sum of the total number of accesses made from more than
    #      distance 1 from the cache nodes.
    #  best_possible_cost - C * nodes_in_cache + sum(min(total accesses, C) for non-cache nodes)
    #  *** Sufficient data structures to be able to unwind changes to all of the above before
    #      returning from recursive calls (I won't specify accesses to them, but they need to
    #      be there)
    #  best_cost - The best cost found.
    #  best_solution - The best solution found.
    
    initialize all of those data structures (including best)
    create neighbors priority queue of neighbors of root cache node (ordered by accesses)
    call extend_current_solution(neighbors)
    do what we want with the best solution
    
    function extend_current_solution (available_neighbors):
        if cost < best_cost:
            best_cost = cost
            best_solution = CA # current set of cache nodes.
        if best_cost < best_possible_cost
            return # pruning time
        neighbors = clone(available_neighbors)
        while neighbors:
            node = remove best from neighbors
            if distant_accesses + accesses(node) < C:
                return # this is condition 3 above
            make node in cache set
                - add it to CA
                - update costs
                - add its immediate neighbors to neighbors
            call extend_current_solution
            unwind changes just made
            make node in blocked set
            call extend_current_solution
        unwind changes to blocked set
        return
    

    编写此代码需要大量工作,并且您需要小心维护所有数据结构。但我敢打赌——尽管它看起来很重——你会发现它修剪了你的搜索空间,足以比你现有的解决方案运行得更快。 (它仍然不会活泼。)

    祝你好运!

    更新

    当我更多地思考这个问题时,我意识到一个更好的观察是注意如果你可以将“不是缓存节点,不是阻塞节点”的集合分成两部分,那么你可以独立解决这些部分。这些子问题中的每一个都比整个问题的解决速度要快几个数量级,因此请尽快解决。

    一个很好的启发式方法是遵循以下规则:

    1. 当没有到达边缘时:
      1. 向最近的边缘行驶。距离是通过最短路径沿着非缓存、非阻塞集合的长度来衡量的。
      2. 如果两条边等距,则根据以下优先顺序断开连接:(1, x), (x, 1), (5, x), (x, 5)。
      3. 根据更喜欢向边缘中心行驶,打破任何剩余的联系。
      4. 随机打破所有剩余的关系。
    2. 虽然已到达边并且您的组件仍有可能成为缓存块的边:
      1. 如果您可以立即移动到边缘并将边缘部分分成两个组件,请执行此操作。对于“缓存集中的边缘”和“不在缓存集中的边缘”,您都会得到 2 个更容易处理的独立子问题。
      2. 否则沿最短路径移动到边缘部分中间的部分。
      3. 如果存在平局,请打破平局,以使从添加的块到添加的缓存元素的线尽可能接近对角线。
      4. 随机打破所有剩余的关系。
    3. 如果你掉到这里,随机选择。 (此时您应该有一个非常小的子问题。无需聪明。)

    如果您从 (3, 3) 作为缓存点开始尝试此操作,您会发现在最初的几个决定中,您会发现 7/16 的时间您设法切入两个偶数问题,1/ 16 次你在缓存点中阻塞并完成,1/4 的时间你设法将一个 2x2 块切割成一个单独的问题(使整个解决方案的运行速度提高 16 倍)和 1/4 的当您在朝着解决方案前进的道路上顺利结束时,该解决方案要么被装箱(并迅速耗尽),要么成为具有大量缓存点的解决方案的候选者,这些缓存点被修剪以实现正轨一个糟糕的解决方案。

    我不会给出这个变体的伪代码。它将与我上面的内容有很多相似之处,需要处理许多重要的细节。但我愿意打赌,实际上它的运行速度会比您原来的解决方案快几个数量级。

    【讨论】:

    • 实现剪枝步骤的更好方法是将问题放松为线性规划,并使用 Simplex 解决它(Simplex 在复杂度上近似线性)。这给出了成本的下限,这两者都告诉哪个分支更有可能包含最佳解决方案(因此深度优先搜索应该首先遵循它)。并且当找到任何解决方案时,可以完全修剪所有边界更差的子树。
    • @ben-voigt:对我来说,如何将这个问题放松为线性规划并不明显。如果您可以在答案中写出关于如何做到这一点的合理描述,我会投赞成票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-27
    • 1970-01-01
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    相关资源
    最近更新 更多