图表中的路径问题:最小化平均边缘成本而不是总成本

时间:2011-08-25 18:27:48

标签: algorithm optimization graph graph-theory shortest-path

我有一个加权图,没有负权重,我想找到从一个节点到另一个节点的路径,试图最小化单步的成本。我不需要最小化旅行的总费用(例如Dijkstra),而是平均步骤费用。但是,我有一个约束:K,路径中的最大节点数。

所以例如从A到J可能Dijkstra会找到这条路径(括号之间的重量)

A (4) D (6) J -> total cost: 10

和我需要的算法,设置K = 10,会找到像

这样的东西
A (1) B (2) C (2) D (1) E (3) F (2) G (1) H (3) J -> total cost: 15

这个问题有没有众所周知的算法?

提前致谢。

欧金尼奥

编辑为templatetypedef的答案。 一些问题:

1)事实上它可能多次采取一个周期来降低平均值对我的问题不利:也许我应该提到它但我不想多次访问同一个节点

2)是否有可能利用我没有负权重的事实?

3)当你说O(kE)时,你的意思是整个算法还是只是附加部分?

让我们在C中采用这个简单的实现,其中n =节点数e =边数,d是带距离的向量,带前驱的pa向量和结构边(u,v,w)记忆边缘图表

for (i = 0; i < n; ++i)
    d[i] = INFINITY;

    d[s] = 0;

    for (i = 0; i < n - 1; ++i)
        for (j = 0; j < e; ++j)
            if (d[edges[j].u] + edges[j].w < d[edges[j].v]){
                d[edges[j].v] = d[edges[j].u] + edges[j].w;
                p[edges[j].v] = u;
                }

我不确定如何根据你的回答修改代码;如果这足够,可以考虑平均值而不是总成本吗?

for (i = 0; i < n; ++i)
        d[i] = INFINITY;

    d[s] = 0;

    for (i = 0; i < n - 1; ++i)
        steps = 0;
        for (j = 0; j < e; ++j)
            if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
                d[edges[j].v] = d[edges[j].u] + edges[j].w;
                p[edges[j].v] = u;
                steps++;
            }

但无论如何我不知道如何同时考虑K限制......再次感谢您的帮助。

修改 由于我能承受一些错误,我正在考虑这个天真的解决方案:

  • 预先计算所有最短路径并记忆在A
  • 预先计算修改过的图表上的所有最短路径,我在那里切割一定重量的边缘并将其记忆在B

当我需要路径时,我会查看A,例如从x到y这是路径 X-&GT; Z-&GT; Y 然后对于每一步我看B, 所以对于x>我看到B中是否有连接,如果没有,我保持x&gt; z否则我填写路径x&gt; z由B提供的子路径,可以是x-> j-> h-> z;然后我对z-&gt; y做同样的事情。 每次我还要检查我是否添加循环路径。

也许我会得到一些奇怪的路径,但它可以在大多数情况下工作。 如果我尝试使用不同的“削减阈值”扩展解决方案,也许我也可以接近尊重K约束。

3 个答案:

答案 0 :(得分:6)

我相信您可以使用贝尔曼 - 福特算法的修改版本来解决这个问题。

Bellman-Ford基于以下动态编程重现,试图找到从一些起始节点到另一个节点的最短路径,对于某些m,长度不大于m。作为基本情况,当您考虑长度为零的路径时,唯一可达的节点是s,初始值为

BF(s, t, 0) = infinity
BF(s, s, 0) = 0

然后,如果我们知道长度为m的路径的值,我们可以通过注意旧路径可能仍然有效,或者我们想要按长度1扩展一些路径来找到长度为m + 1的路径:

BF(s, t, m + 1) = min {
                     BF(s, t, m),
                     BF(s, u, m) + d(u, t) for any node u connected to t
                   }

算法作为一个整体工作,注意任何最短路径必须具有不大于n的长度,然后使用上述递归和动态编程来计算所有t的BF(s,t,n)的值。它的整体运行时间为O(EV),因为每个步骤都需要考虑E边缘和V个顶点。

让我们看看如何更改此算法以解决您的问题。首先,为了将其限制为长度为k的路径,我们可以在找到长度最长为k的所有最短路径后切断Bellman-Ford迭代。找到平均成本最低的路径有点棘手。在每个点,我们将跟踪两个量 - 到达节点t的最短路径的长度和该路径的平均长度。当考虑可以达到t的新路径时,我们的选择是保持我们找到的早期路径(其成本由迄今为止的最短路径除以其中的节点数)或者将一些其他路径延伸一步。然后,该路径的新成本由之前的总成本加上边长度除以旧路径中的边数加1。如果我们采用最便宜的这些,然后记录其成本和边缘数量,最后我们将计算具有最小平均成本长度不大于k的时间O(kE)的路径。作为初始化,我们将说从起始节点到其自身的路径长度为0且平均成本为0(平均成本无关紧要,因为每当我们将它乘以得到0的边数时)。我们还会说,每个其他节点都处于距离无穷大,说边缘的平均成本是无穷大,边缘的数量是1。这样,如果我们尝试计算通过扩展路径形成的路径的成本,它将看起来具有平均成本无穷大并且将不会被选择。

数学上,解决方案看起来像这样。在每个点,我们存储平均边缘成本和每个节点的边缘总数:

BF(s, t, 0).edges = 1
BF(s, t, 0).cost  = infinity

BF(s, s, 0).edges = 0
BF(s, s, 0).cost  = 0

BF(s, t, m + 1).cost = min {
    BF(s, t, m).cost,
    (BF(s, u, m).cost * BF(s, u, m).edges + d(u, t)) / (BF(s, u, m).edges + 1)
}

BF(s, t, m + 1).edges = {
    BF(s, t, m).edges         if you chose the first option above. 
    BF(s, u, m).edges + 1     else, where u is as above
}

请注意,这可能找不到长度为k的简单路径,因为最小化平均成本可能需要您多次采用低(正或负)成本的周期来降低平均值。例如,如果图形具有成本为零的循环,您应该尽可能多地使用它。

编辑:在回答您的新问题时,如果您不想复制路径上的节点,则此方法将无效。正如@comestibles指出的那样,这个版本的问题是NP难的,所以除非P = NP,否则你不应该期望找到任何好的多项式时间算法来解决这个问题。

对于运行时,我上面描述的算法以总时间O(kE)运行。这是因为计算递归的每次迭代都需要O(E)时间,并且总共有k次迭代。

最后,让我们看看你提出的代码。我在这里重印了它:

for (i = 0; i < n - 1; ++i) {
    steps = 0;
    for (j = 0; j < e; ++j) {
        if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
            d[edges[j].v] = d[edges[j].u] + edges[j].w;
            p[edges[j].v] = u;
            steps++;
        }
    }
}

你的第一个问题是如何考虑k。这可以很容易地通过重写外部循环来计算到k,而不是n - 1.这给了我们这段代码:

for (i = 0; i < k; ++i) {
    steps = 0;
    for (j = 0; j < e; ++j) {
        if ( (d[edges[j].u]+ edges[j].w)/(steps+1) < d[edges[j].v]/steps){
            d[edges[j].v] = d[edges[j].u] + edges[j].w;
            p[edges[j].v] = u;
            steps++;
        }
    }
}

我注意到的一个问题是修改后的Bellman-Ford算法需要让每个候选最佳路径独立地存储其边数,因为每个节点的最佳路径可能由不同数量的边缘到达。为了解决这个问题,我建议让d数组存储两个值 - 到达节点所需的边数和沿该路径的节点的平均成本。然后,您可以通过使用缓存的路径长度替换这些方程中的steps变量来更新代码。

希望这有帮助!

答案 1 :(得分:2)

对于你的问题的新版本,从汉密尔顿路径减少(使你的问题难以处理)。以Hamilton路径的实例(即,假设其边缘具有单位权重的图形),将源和汇顶点和权重2的边缘从源添加到所有其他路径以及从接收器到所有其他路径。设置K = | V | + 2并请求从源到接收器的路径。当且仅当最佳平均边长为(| V | + 3)/(| V | + 2)时,才存在Hamilton路径。

请注意告诉我们您为何希望使用这些路径,以便我们为您提供合理的近似策略建议?

答案 2 :(得分:0)

您可以稍微修改Bellman-Ford算法,以找到最多使用K个边/节点的最小路径。 如果边数是固定的,那么最小化总成本,因为平均成本是TotalCost / NumberOfEdges。

其中一个解决方案是将NumberOfEdges从1迭代到K,找到最小的总成本并选择最小的TotalCost / NumberOfEdges。