获取使用动态编程获得的解决方案的实际步骤

时间:2011-10-19 13:43:21

标签: algorithm dynamic-programming

如果使用动态编程来获得问题的最佳解决方案。您如何重建导致该解决方案的实际步骤?

例如,在0-1背包问题中,您使用了重复

enter image description here

使用这个我们可以获得背包中可以存在的最大值。你如何找到实际的物品。

这可以推广到任何动态编程解决方案。例如。找到实际的nos,它们是使用动态编程获得解决方案的最长增长子序列的一部分。

4 个答案:

答案 0 :(得分:4)

  

这可以针对任何动态编程解决方案进行推广。

不,你不能一般通过检查DP表中的最终值来找到实际的解决方案。

如果算法只是寻找某个最佳值,它通常会丢弃有关 每个值的计算信息。

在DP解决方案中,行 R 上的单元格可能例如取决于行 R-1 中的最大值。除非算法记录了选择的单元格,否则将无法根据结果表重建实际解决方案。

但是,您应始终能够将附加信息附加到描述值来源的每个单元格,例如引用当前单元格依赖的先前计算的单元格,并使用此信息重建实际的解决方案。

答案 1 :(得分:2)

诀窍是存储其他信息,这些信息将允许您重新构建在每个步骤中所做的选择,同时填充动态编程表。有时,表本身包含此类信息。例如,在0/1背包问题中,您可以通过以下方式找到用于达到最佳解决方案的项目(请注意,只需要表格):

# 0/1 knapsack. O(nC) time, O(nC) space,
# also returns the index of the items to pick
# V: values, W: weights, C: capacity

def integral_knapsack_items(V, W, C):
    table = integral_knapsack_table(V, W, C)
    i, j, items = len(W), C, []
    while i != 0 and j != 0:
        if table[i][j] != table[i-1][j]:
            items.append(i-1)
            i, j = i-1, j-W[i-1]
        else:
            i -= 1
    return (table[-1][-1], items)

def integral_knapsack_table(V, W, C):
    m, n = len(W)+1, C+1
    table = [[0] * n for x in xrange(m)]
    for i in xrange(1, m):
        for j in xrange(1, n):
            if W[i-1] > j:
                table[i][j] = table[i-1][j]
            else:
                table[i][j] = max(table[i-1][j],
                                  V[i-1] + table[i-1][j-W[i-1]])
    return table

在上面的代码中,您使用integral_knapsack_items()(值数组),V(相应权重数组)和W(容量为{1}}来调用C背包),程序返回一个元组,其中包含填充背包时获得的最大值,以及用于达到该值的项目的索引。

答案 2 :(得分:1)

您只需要重新访问DP中的步骤即可。在0-1背包的情况下,假设原始DP功能是求解,函数 reconstruct 将为您提供实际解决方案(我用C ++编写代码) ):

int solve(int pos, int capacity)
{
    if(pos == no_of_objects) return 0;
    if(memo[pos][capacity] != -1) return memo[pos][capacity];

    int r1 = solve(pos + 1, capacity); //dont take
    int r2 = 0;
    if(weight[pos] <= capacity)
    {
        r2 = solve(pos + 1, capacity - weight[pos]) + profit[pos]; //take
    }
    return memo[pos][capacity] = max(r1, r2);
}

void reconstruct(int pos, int capacity)
{
    if(pos == no_of_objects) return; //you have completed reconstruction

    int r1 = memo[pos + 1][capacity]; //dont take
    int r2 = 0;
    if(weight[pos] <= capacity)
        r2 = memo[pos + 1][capacity - weight[pos]] + profit[pos]; //take

    if(r1 > r2) 
    {
        reconstruct(pos + 1, capacity);
    }
    else
    {
        cout << "Take object " << pos << endl;
        reconstruct(pos + 1, capacity - weight[pos]) + profit[pos]; 
    }
}

执行 reconstruct 后,它将打印所有那些为您提供最佳解决方案的对象。如您所见,最多 no_of_objects 调用将在 reconstruct 函数中进行。
同样,你可以贪婪地重建任何DP的解决方案。

答案 3 :(得分:0)

大多数动态编程算法都使用Memoization和Backtracking。 Memoization是一种查找表,其中算法存储每个步骤的状态信息。算法完成后,它会使用回溯从算法的最后一个状态转到前一个状态。在knapsak上,这可以通过存储“我来自哪里?”来获得。值。如何计算M [i,w]?来自m [i-1.w]或m [i-1,w-wi] + vi。搜索Memoization和Backtracking以获得更多示例。