【问题标题】:Recursion to iteration conversion using dynamic programming使用动态规划递归到迭代转换
【发布时间】:2017-10-10 04:04:14
【问题描述】:
    public static int n;
    public static int w;
    public static int[] s;
    public static int[] p;

    static void Main(string[] args)
    {
        n = 5;
        w = 5;

        s = new int[n + 1];
        p = new int[n + 1];
        Random rnd = new Random();

        for (int i = 1; i <= n; i++)
        {

            s[i] = rnd.Next(1, 10);
            p[i] = rnd.Next(1, 10);
        }

        Console.WriteLine(F_recursion(n, w));
        Console.WriteLine(DP(n, w));
    }

    // recursive approach
    public static int F_recursion(int n, int w)
    {
        if (n == 0 || w == 0)
            return 0;
        else if (s[n] > w)
            return F_recursion(n - 1, w);
        else
        {                          
            return Math.Max(F_recursion(n - 1, w), (p[n] + F_recursion(n - 1, w - s[n])));
        }
    }

    // iterative approach
    public static int DP(int n, int w)
    {
        int result = 0;

        for (int i = 1; i <= n; i++)
        {

            if (s[i] > w)
            {
                continue;
            }
            else
            {                   
                result += p[i];
                w = w - s[i];
            }
        }

        return result;
    }

我需要将 F_recursion 函数转换为迭代。我目前编写了以下功能 DP,该功能有时有效,但并非总是如此。我了解到问题出在 F_recursion(n - 1, w - s[n]) 我不知道如何使 w - s[n] 在迭代解决方案中正常工作。如果将 w - s[n] 和 w - s[i] 更改为仅 w,则程序始终可以工作。

在控制台中:

s[i] = 2 p[i] = 3
-------
s[i] = 3 p[i] = 4
-------
s[i] = 5 p[i] = 3
-------
s[i] = 3 p[i] = 8
-------
s[i] = 6 p[i] = 6
-------
Recursive:11
Iteration:7

但有时它会起作用

s[i] = 5 p[i] = 6
-------
s[i] = 8 p[i] = 1
-------
s[i] = 3 p[i] = 5
-------
s[i] = 3 p[i] = 1
-------
s[i] = 7 p[i] = 7
-------
Recursive:6
Iteration:6

【问题讨论】:

  • 定义“有时有效,但不总是”。
  • 添加了我多次运行程序后得到的结果。
  • 好的,太好了......现在这些数字是什么意思?你的递归方法实际上是做什么的?
  • 方法是递归关系,我将其转换为递归版本,效果很好,但我不知道如何将其转换为迭代版本。 s, p 只是我存储随机数的数组。
  • 这就是它的本质,而不是它的作用。无论如何,您的迭代方法看起来不像您的递归方法。你没有检查w 的值,没有调用Math.Max,我看不出它会如何复制可能的分支递归。据我所知,您已经实现了足够多的迭代方法,以便仅在非常特殊的输入类型上复制递归方法。

标签: c# dynamic-programming memoization


【解决方案1】:

以下方法可能有用,当涉及更大的数字时(特别是对于s),因此二维数组将不必要大,并且实际上只有少数w 值会用于计算结果。

想法:预先计算可能的w 值,从w 开始,并为每个i in [n, n-1, ..., 1] 确定w_[i] 的值,其中w_[i+1] &gt;= s[i] 不重复。 然后在 n 上迭代 i_n 并仅计算有效 w_[i] 值的子结果。

我选择了一个Dictionary 的数组作为数据结构,因为这样设计稀疏数据相对容易。

public static int DP(int n, int w)
{
    // compute possible w values for each iteration from 0 to n
    Stack<HashSet<int>> validW = new Stack<HashSet<int>>();
    validW.Push(new HashSet<int>() { w });
    for (int i = n; i > 0; i--)
    {
        HashSet<int> validW_i = new HashSet<int>();
        foreach (var prevValid in validW.Peek())
        {
            validW_i.Add(prevValid);
            if (prevValid >= s[i])
            {
                validW_i.Add(prevValid - s[i]);
            }
        }
        validW.Push(validW_i);
    }

    // compute sub-results for all possible n,w values.
    Dictionary<int, int>[] value = new Dictionary<int,int>[n + 1];
    for (int n_i = 0; n_i <= n; n_i++)
    {
        value[n_i] = new Dictionary<int, int>();
        HashSet<int> validSubtractW_i = validW.Pop();
        foreach (var w_j in validSubtractW_i)
        {
            if (n_i == 0 || w_j == 0)
                value[n_i][w_j] = 0;
            else if (s[n_i] > w_j)
                value[n_i][w_j] = value[n_i - 1][w_j];
            else
                value[n_i][w_j] = Math.Max(value[n_i - 1][w_j], (p[n_i] + value[n_i - 1][w_j - s[n_i]]));
        }
    }

    return value[n][w];
}

重要的是要理解一些空间和计算是“浪费的”,以便预先计算可能的 w 值并支持稀疏数据结构。因此,这种方法可能对s 中具有 值的大型数据集表现不佳,其中大多数w 值可能是子结果。

经过深思熟虑后,我意识到,如果空间是一个问题,您实际上可以丢弃除先前外循环迭代之外的所有内容的子结果,因为该算法中的递归遵循严格的n-1 模式。但是,我暂时不将其包含在我的代码中。

【讨论】:

    【解决方案2】:

    您的方法不起作用,因为您的动态程序状态空间(显然只有一个变量)与递归方法的签名不匹配。动态规划方法的目标应该是定义和填充状态空间,以便在需要时可以使用所有评估结果。在检查递归方法时,请注意F_recursion 的递归调用可能会更改nw 这两个参数。这表明应该使用二维状态空间。

    第一个参数(显然限制了项目的范围)的范围可以从 0n,而第二个参数(显然是某个项目属性的总和)的范围可以从 0w.

    你应该定义一个二维状态空间

    int[,] value = new int[n,w];
    

    用于容纳值。接下来,您应该将值初始化为未定义;您可以为此使用值 Int32.MaxValue,因为如果计算出具有不同值的最小值,它将以合适的方式运行。

    接下来,算法的迭代版本应该使用两个以向前方式迭代的循环,这与减少参数的递归迭代不同。

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < w; j++)
        {
            // logic for the recurrence relation goes here
        }
    }
    

    在最里面的块中,您可以使用递归关系的修改版本。您可以访问存储在value 中的值,而不是使用递归调用;您将值写入value,而不是返回值。

    在语义上这与memoization 相同,但不是使用实际的递归调用,而是评估顺序断言必要的值始终存在,从而不需要额外的逻辑。

    一旦状态空间被填满,您必须检查其最后一个状态(即数组中第一个索引为n-1 的部分)以确定整个输入的最大值。

    【讨论】:

    • 对于随机值 w 值中的间隙,大量计算将被浪费...
    • @grek40 你的意思是评估的状态比必要的多?
    • 是的......我觉得有一种方法可以通过使用sum(s[i]) &lt;= w 限制有效子集来限制计算的 w 值。但是还没有完成这个思路。
    • @grek40 你是对的;但简单地向递归实现添加记忆并不会删除递归(问题明确要求删除)。
    猜你喜欢
    • 2011-11-09
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-26
    • 2013-11-14
    • 2012-07-10
    相关资源
    最近更新 更多