【问题标题】:Speeding up a function with dynamic programming使用动态规划加速函数
【发布时间】:2011-02-13 01:43:33
【问题描述】:

我有这个程序

//h is our N
    static int g=0;
    int fun(int h){
        if(h<=0){
                  g++;
                  return g;
                  }
    return g+fun(h-1)+fun(h-4);
    }

是否可以使用动态编程来加快速度?

我发现这个函数在 O(2^n) 中运行

我应该通过动态编程来减少运行时间,但不明白这个概念。

只是要求朝正确的方向轻推。

【问题讨论】:

  • @Michael 我认为@aristotaly 指的是大 O 符号 thing-a-ma-jig。
  • 作业标签没有被删除为所有伪标签(本身无用分类问题)?

标签: c++ dynamic-programming


【解决方案1】:

如果我们定义 g+fun(h-1)+fun(n-4) 中的求和顺序是从左到右,那么这是一个很好定义的问题。这样我得到了 fun(n), n=1,...,15 的值:

3, 6, 10, 15, 33, 74, 154, 295, 575, 1143, 2269, 4414, 8508, 16396, 31634

fun(n) 的返回值被评估为具有非降序元素的求和序列。每个加数都比前一个大(返回 g++;)或与前一个相同(返回 g+fun()+fun())。返回语句的执行顺序仅取决于 fun() 输入参数。因此,将 g 设置为初始值!= 0 我们得到与 g=0 相同的加数,但对于相同的初始值,每个加数都更大。 这样,初始 g > 0 的 fun(n) 将返回 g * 执行的返回语句数 大于初始 g = 0 的值。

将A(n)定义为执行fun(n)时执行的return语句数,在if子句中定义G(n)执行return语句数(与g++语句执行数相同) .对于 A 和 G 成立:

A(n) = A(n-1) + A(n-4) + 1
G(n) = G(n-1) + G(n-4)
A(n) = 1 and G(n) = 1, for n <= 0

从这些观察可以看出,对于 n > 0 成立:

fun(n) = fun(n-1) + G(n-1) * A(n-4) + fun(n-4)

简单的python实现:

def x( h ):
  Rg = { -3:1, -2:1, -1:1, 0:1 }
  Ra = { -3:1, -2:1, -1:1, 0:1 }
  F  = { -3:1, -2:1, -1:1, 0:1 }
  for i in xrange( 1, h+1 ):
    F[i] = F[i-1] + Rg[i-1]*Ra[i-4] + F[i-4]
    print i, F[i]
    Rg[i] = Rg[i-1] + Rg[i-4]
    Ra[i] = Ra[i-1] + Ra[i-4] + 1

@stakx:对于表达式 g+fun(h-1)+fun(h-4),我们不能保证评估顺序,尤其是在 C 语言中。

【讨论】:

  • 如果您尝试使用 C 函数,即使无法保证评估顺序,您也会看到您的 python 函数不会返回相同的结果。你的分析中应该有一个错误。你是不是很开心返回返回语句的数量*g。对我来说似乎并不明显。
  • @kriss:假设 g+fun(n-1)+fun(n-4) 是从左到右计算的,而不是 fun(1)=0+fun(0)+fun( -3)=0+1+2=3。当我运行这个 C 实现时,我得到 fun(1)=4。换行:return g+fun(h-1)+fun(h-4);带行:int aa = g;返回 aa+fun(h-1)+fun(h-4);我得到 fun(1) = 3。差异可能在于优化。
  • 是的。一旦我从左到右强制评估顺序,我得到了相同的结果。
【解决方案2】:

好的。我们从 fun(强制评估顺序的序列化版本)开始。

int fun(int h){
    if(h<=0){
         g++;
         return g;
    }
    int tmp1 = g;
    int tmp2 = fun(h-1);
    int tmp3 = fun(h-4);
    return tmp1+tmp2+tmp3;
}

让我们关注g,忘记函数的当前结果

现在可以很容易地更改函数以将 g 作为参数传入并返回 g 的新值作为结果。

int gun(int h, int g0){
    if(h<=0){
         return g0+1;
    }
    int tmp1 = g0;
    int tmp2 = gun(h-1, g0);
    int tmp3 = gun(h-4, tmp2);
    return tmp3;
}

What can be simplified into:

int gun(int h, int g0){
    if(h<=0){
         return g0+1;
    }
    return gun(h-4, gun(h-1, g0));
}

现在回到乐趣:

int fun2(int h, int g0){
    if(h<=0){
         return g0+1;
    }
    return g0+fun2(h-1, g0)+fun2(h-4, gun(h-1,g0));
}

fun2 做的事情和最初的 fun 完全一样,但是现在我们去掉了副作用并且函数只依赖于它的参数,我们可以记忆结果(将已经计算的结果存储在一个数组中),这应该会加速计算。

我们仍然可以稍微简化一下枪。 g0参数不是必须的,我们设置为0吧。

int gun2(int h){
    if(h<=0){
         return 1;
    }
    return gun2(h-4) + gun2(h-1);
}

我们甚至可以定义一个fun3,将g0参数固定为0,这样会更简单一些,但它仍然需要调用fun2。我还没有看到如何进一步简化,但它可能是可能的。

int fun3(int h){
    if(h<=0){
         return 1;
    }
    return fun3(h-1)+fun2(h-4, gun2(h-1));
}

【讨论】:

  • 在第二个重写步骤中,我对int tmp3 = gun(h-4, tmp2);有些困惑。为什么要为参数g 传入tmp2?此外,由于函数可能会更改g,不应该返回g 的当前值,即。该函数不应该返回一个(二)元组吗?
  • @jpalecek:为了加速,一旦我们有了真正的函数而没有副作用,记忆(保持已经计算的值)很容易(即使它可能会占用一些空间)。当您说“这不起作用”时,我不明白您在说什么。我的 gun 函数不应该计算与 fun 相同的东西,而只是为 g 返回相同的值,就像在将其设置为 0 并调用具有副作用的初始 fun 之后一样。 gun 显然不会返回与 fun 相同的值。但你可能在说别的东西?我可能犯了一些错误。
【解决方案3】:

是的,可以使用 DP 来加速它并避免使用 CPU 堆栈,但我同意 stakx 关于更改全局静态变量 g 的副作用。 最好提供一个数学表达式,因为上面的递归函数可能会根据调用的顺序和次数给出不同的结果。

【讨论】:

    【解决方案4】:

    虽然我无法回答您的实际问题,但我对完全不同的东西很感兴趣,即陈述

    return g+fun(h-1)+fun(n-4);
    

    显然,您的函数具有更改全局静态变量g 的副作用。我不能 100% 确定 return 语句的表达式实际上是否以明确定义的方式计算,或者结果是否可能未定义。

    考虑这些函数调用的执行顺序以及这如何影响g 以及函数的结果可能是一个很好的练习。

    【讨论】:

    • 这不是问题吗?练习的重点似乎是消除副作用。
    • @kriss:问题是,在这段代码中,副作用实际上并没有产生明确的结果。
    • @jplacek:是的,我们必须在消除副作用之前在可能的行为之间选择一种行为(以及 g 初始值的影响)。以数学方式定义函数会好得多。
    猜你喜欢
    • 1970-01-01
    • 2017-12-29
    • 2016-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-14
    • 1970-01-01
    • 2012-10-17
    相关资源
    最近更新 更多