Algorithm 自顶向下动态规划算法的运行时间

Algorithm 自顶向下动态规划算法的运行时间,algorithm,time,time-complexity,dynamic-programming,Algorithm,Time,Time Complexity,Dynamic Programming,我为一项任务提出了以下算法。主要是第23-24行的for循环,这让我不确定 function TOP-DOWN-N(C, n, p) let N[1...n, 1...C] be a new array initialized to -1 for all indicies return N(C, n) function N(C, n) if N[C,n] >= 0 then return N[C,n] if C < 0 or n < 1 then

我为一项任务提出了以下算法。主要是第23-24行的for循环,这让我不确定

function TOP-DOWN-N(C, n, p)
  let N[1...n, 1...C] be a new array initialized to -1 for all indicies
  return N(C, n)

function N(C, n)
  if N[C,n] >= 0 then
    return N[C,n]
  if C < 0 or n < 1 then
    return 0
  elif C = 0 then
    return 1
  elif C > 0 and i > 0 then
    r = 0
    for i = 0 to n do
      r += N(C-p[n-i], n-(i+1))
    N[C,n] = r
    return N
函数自顶向下-N(C,N,p)
设N[1…N,1…C]是一个新数组,对于所有标记,该数组初始化为-1
返回N(C,N)
函数N(C,N)
如果N[C,N]>=0,则
返回N[C,N]
如果C<0或n<1,则
返回0
如果C=0,则
返回1
如果C>0,i>0
r=0
对于i=0到n do
r+=N(C-p[N-i],N-(i+1))
N[C,N]=r
返回N

让我们忽略一个事实,即该算法是递归实现的。通常,如果动态规划算法正在构建一个由N个结果组成的数组,并且计算每个结果需要使用该数组中k个其他结果的值,则其时间复杂度以Ω(Nk)为单位,其中Ω表示下限。这一点应该很清楚:使用k值计算结果需要Ω(k)时间,您必须这样做N次

另一方面,如果计算没有做任何比从数组中读取k值更耗时的事情,那么O(Nk)也是一个上界,因此时间复杂度为Θ(Nk)


因此,根据这种逻辑,我们应该期望算法的时间复杂度为Θ(n2C),因为它构建了一个大小为nC的数组,计算每个结果时使用该数组中的Θ(n)其他结果,并且计算不会被其他结果所支配

但是与迭代实现相比,您的算法具有优势,因为它不一定计算数组中的每个结果。例如,如果数字1不在数组
p
中,则您的算法不会为任何
N'
计算
N(C-1,N')
;如果
p
中的数字都大于或等于C,则循环只执行一次,并且运行时间主要取决于必须初始化大小为nC的数组


因此,Θ(n2C)是最坏情况下的时间复杂度,而最佳情况下的时间复杂度是Θ(nC)。

让我们忽略这个算法是递归实现的事实。通常,如果动态规划算法正在构建一个由N个结果组成的数组,并且计算每个结果需要使用该数组中k个其他结果的值,则其时间复杂度以Ω(Nk)为单位,其中Ω表示下限。这一点应该很清楚:使用k值计算结果需要Ω(k)时间,您必须这样做N次

另一方面,如果计算没有做任何比从数组中读取k值更耗时的事情,那么O(Nk)也是一个上界,因此时间复杂度为Θ(Nk)


因此,根据这种逻辑,我们应该期望算法的时间复杂度为Θ(n2C),因为它构建了一个大小为nC的数组,计算每个结果时使用该数组中的Θ(n)其他结果,并且计算不会被其他结果所支配

但是与迭代实现相比,您的算法具有优势,因为它不一定计算数组中的每个结果。例如,如果数字1不在数组
p
中,则您的算法不会为任何
N'
计算
N(C-1,N')
;如果
p
中的数字都大于或等于C,则循环只执行一次,并且运行时间主要取决于必须初始化大小为nC的数组


因此,Θ(n2C)是最坏情况下的时间复杂度,而最佳情况下的时间复杂度是Θ(nC)。

至于分析,您是正确的,最多O(nC)调用可能需要O(1)以上的时间,但您仍然必须计算那些需要O(1)时间的调用,因为它们可能很多。需要O(1)时间的调用时间不会改变算法的运行时间为O(nC),对吗?现在我将用伪代码把它打出来。如果循环发生的地方有O(nC)调用,并且每个调用都有一个O(n)次迭代的循环,那么总的复杂度应该是O(n^2C),不是吗?这是假设循环体需要恒定的时间,严格来说这不是真的,但这是一种有效的简化,因为从循环中进行的递归调用已经被考虑在内。但是,尽管如此,循环本身进行O(n)函数调用和O(n)加法。让我恼火的是-如果函数调用终止时为N[C,N]>=0,那么这将不会到达for循环。如果要使
N[C,N]>=0
条件为真,那么它在过去的某个时候一定是假的,值为
C
N
,否则,该数组单元将永远不会被写入。至于分析,您是正确的,大多数O(nC)调用可能需要O(1)以上的时间,但您仍然必须计算需要O(1)时间的调用,因为可能会有很多。需要O(1)时间的调用不会改变算法的运行时间为O(nC),对的现在我将用伪代码把它打出来。如果循环发生的地方有O(nC)调用,并且每个调用都有一个O(n)次迭代的循环,那么总的复杂度应该是O(n^2C),不是吗?这是假设循环体需要恒定的时间,严格来说这不是真的,但这是一种有效的简化,因为从循环中进行的递归调用已经被考虑在内。但是,尽管如此,循环本身进行O(n)函数调用和O(n)加法。让我恼火的是,如果函数调用终止为N[C,N]>=0,那么这将不会到达for循环。如果
N[C,N]>=0
条件为真,那么它在过去的某个时候一定是假的,值为
C
N
,否则该数组单元将永远不会被写入。