如何为给定的代码编写递归关系

Posted

技术标签:

【中文标题】如何为给定的代码编写递归关系【英文标题】:how to write a recurrence relation for a given piece of code 【发布时间】:2015-07-23 22:45:02 【问题描述】:

在我的算法和数据结构课程中,我们得到了一些递归关系来解决或者我们可以看到算法的复杂性。

起初,我认为这些关系的唯一目的是记下递归分治算法的复杂性。然后我在麻省理工学院的作业中遇到了一个问题,要求提供迭代算法的递归关系。

如果给定一些代码,我将如何自己提出递归关系?有哪些必要步骤?

我可以记下任何情况,即具有这种关系的最坏、最好、平均情况,这实际上是否正确?

有人可以举一个简单的例子来说明一段代码是如何变成递归关系的吗?

干杯, 安德鲁

【问题讨论】:

【参考方案1】:

好的,所以在算法分析中,递归关系是将解决大小为 n 的问题所需的工作量与解决较小问题所需的工作量相关联的函数(这与它在数学中的含义密切相关)。

例如,考虑下面的斐波那契函数:

Fib(a) 

  if(a==1 || a==0)
    return 1;
  return Fib(a-1) + Fib(a-2);

这会进行三个操作(比较、比较、加法),并且还会递归调用自身。所以递归关系是T(n) = 3 + T(n-1) + T(n-2)。为了解决这个问题,您将使用迭代方法:开始扩展术语,直到找到模式。对于此示例,您将扩展 T(n-1) 以获取 T(n) = 6 + 2*T(n-2) + T(n-3)。然后展开T(n-2) 得到T(n) = 12 + 3*T(n-3) + 2*T(n-4)。再一次,扩展T(n-3) 得到T(n) = 21 + 5*T(n-4) + 3*T(n-5)。请注意,第一个 T 项的系数遵循斐波那契数,而常数项是它们乘以 3 的总和:查找它,即3*(Fib(n+2)-1)。更重要的是,我们注意到序列呈指数增长;也就是算法的复杂度是O(2n)。

然后考虑这个函数进行归并排序:

Merge(ary)

  ary_start = Merge(ary[0:n/2]);
  ary_end = Merge(ary[n/2:n]);

  return MergeArrays(ary_start, ary_end);

这个函数在输入的一半上调用自己两次,然后合并两半(使用 O(n) 工作)。即T(n) = T(n/2) + T(n/2) + O(n)。要解决这种类型的递归关系,您应该使用Master Theorem。根据这个定理,这扩展为T(n) = O(n log n)

最后,考虑这个函数来计算斐波那契:

Fib2(n)

  two = one = 1;
  for(i from 2 to n)
  
    temp = two + one;
    one = two;
    two = temp;
  
  return two;

这个函数不调用自身,它迭代 O(n) 次。因此,其递推关系为T(n) = O(n)。这是你问的情况。它是没有递归的递归关系的特例;因此,很容易解决。

【讨论】:

很好的答案。很好的解释。非常感谢:-) 如何计算?在我的步骤中,T(n-1) = 9 + 2(T(n-1)+T(n-3)),哪里错了? @linsir 你的问题没有意义,你的左右两边都有T(n-1)。我假设你问的是第一个斐波那契函数;使用T(n) = const + T(n-1) + T(n-2) 的定义,您将能够证明右侧的第一个T 项遵循斐波那契。 (我使用了const = 3,但你可以使用任何常量。)【参考方案2】:

要找到算法的运行时间,我们首先需要能够为算法编写一个表达式,该表达式告诉每个步骤的运行时间。因此,您需要遍历算法的每个步骤才能找到表达式。 例如,假设我们定义了一个谓词 isSorted,它将一个数组 a 和数组的大小 n 作为输入,并且当且仅当数组按升序排序时才返回 true。

bool isSorted(int *a, int n) 
   if (n == 1)
      return true;       // a 1-element array is always sorted
   for (int i = 0; i < n-1; i++) 
      if (a[i] > a[i+1]) // found two adjacent elements out of order
         return false;
   
   return true;         // everything's in sorted order

很明显,这里输入的大小只是数组的大小 n。对于输入 n,在最坏情况下将执行多少步?

第一个 if 语句计为 1 步

for循环在最坏的情况下将执行n-1次(假设内部测试没有把我们踢出去),循环测试和索引增量的总时间为n-1 .

在循环内部,还有另一个 if 语句,每次迭代都会执行一次,总共 n-1 次,最坏的情况。

最后一次返回会执行一次。

所以,在最坏的情况下,我们将完成 1+(n−1)+(n−1)+1

计算,对于总运行时间 T(n)≤1+(n-1)+(n-1)+1=2n,因此我们有时序函数 T(n)=O(n)。

简而言之,我们所做的是-->>

1.对于给出输入大小的参数'n',我们假设每个执行一次的简单语句将花费恒定时间,为简单起见假设一个

2.循环和内部正文等迭代语句将根据输入的不同花费不同的时间。 它有解 T(n)=O(n),就像非递归版本一样。

3.所以你的任务是一步一步地写下n的函数来计算时间复杂度

对于递归算法,您执行相同的操作,只是这次您添加了每个递归调用所花费的时间,表示为它在其输入上花费的时间的函数。 比如我们重写一下,isSorted为递归算法:

bool isSorted(int *a, int n) 
   if (n == 1)
      return true;
   if (a[n-2] > a[n-1])         // are the last two elements out of order?
      return false;
   else
      return isSorted(a, n-1); // is the initial part of the array sorted?

在这种情况下,我们仍然遍历算法,计算:第一个 if 1 步加上第二个 if 1 步,加上时间 isSorted 将采用大小为 n−1 的输入,即 T(n -1),给出递归关系

T(n)≤1+1+T(n-1)=T(n-1)+O(1)

有解 T(n)=O(n),就像非递归版本一样。

够简单!!练习更多写各种算法的递归关系记住算法中每个步骤将执行多少时间

【讨论】:

以上是关于如何为给定的代码编写递归关系的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Eclipse 编写代码模板?

如何为 CoreData 多对多关系编写 NSPredicate

如何为以下异常处理代码编写junit测试用例?

如何为罗技鼠标编写简单的 Lua 代码?

如何为 Unity3D 编写线程安全的 C# 代码?

如何为以下代码段编写单元测试用例