【问题标题】:What is the difference between these two recursive approaches这两种递归方法有什么区别
【发布时间】:2016-02-29 22:49:19
【问题描述】:

我有两个版本的相同算法,它们递归地找到链表中的最小元素。一种算法首先检查是否已到达列表的末尾,如果已到达,则返回该元素,然后检查当前元素(列表的头部)是否小于递归调用的当前最小值。如果当前元素更大,则返回当前最小值(通过递归调用找到)。

另一种算法的作用只是略有不同,在检查基本情况之后,它将递归调用存储在一个临时变量中,然后使用 temp 与当前列表元素进行比较。

我发现第一种方法的重复是:

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

(我不太确定)

我想不通的是,第二种算法的重复出现有什么不同,因为它们似乎在做同样的事情。存储在 temp 变量中的递归调用是否会为递归增加额外的工作?

两种算法的伪代码如下:

第一种方法

function min_list_1(L) // L is a non-empty list of numbers
    if has_only_one_element(L): return L.head
    if L.head < min_list_1(L.next): return L.head
    else:
        return min_list_1(L.next)

第二种方法

function min_list_2(L) // L is a non-empty list of numbers
    if has_only_one_element(L): return L.head
    temp = min_list_2(L.next)
    if L.head < temp: return L.head
    else:
        return temp

【问题讨论】:

    标签: algorithm recursion recurrence


    【解决方案1】:

    第一个将在大多数情况下进行两次递归调用(当 L.head 不是找到的最小值时):一次评估条件,第二次返回新值。这会导致 2^n 复杂度。使用 temp 保持线性。

    【讨论】:

    • @loremIpsum1771 在递归的每个步骤中,您对 min_list_1() 进行 2 次调用。因此,在第一步中,您将总共拨打 2 次电话,第二次 - 4 次,第三次 - 8 次,依此类推。您可以通过某个全局计数器计算对min_list_1() 的调用总数。
    • @loremIpsum1771 没问题,确实“用内存来存储递归的结果,这样我们就不用重新计算了”有一个非常有名的技术叫做“动态编程”,就是“递归+记忆” ”。你可以看到更多关于它
    • 额外的调用没有给算法增加任何东西;它完全复制了第一次调用的工作。几乎没有隐藏的认知问题是我们人类倾向于将整个呼叫抽象为一个符号。有些人一开始不会注意到 min_list_1(L.next) 的第二次出现复制了第一次的努力——每一次都明显超过 min_list_2会做的。
    • 完全正确:每个级别的调用次数翻倍。相比之下,在另一个例程中本地完成的工作是 1^n,每个级别减少到 O(1)。
    • 我想是的。当我在基础课程中学习复杂性时,我们并没有相当这么详细,所以我不是审查你最终答案的最佳人选。
    【解决方案2】:

    loremIpsum1771的回答之上,我在这里给你一个简单的类比:

    for(int i=0; i< strlen(s); i++){
         ...
    }
         
         
         vs
         
    for(int i=0, len=strlen(s); i<len; i++){
         ...
    }

    哪个更快,为什么?

    第一个要慢得多,原因与您的 OP 完全相同,您每次/多次调用某些东西,而您确实可以预先计算一次

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-08
      • 2019-03-31
      • 1970-01-01
      • 2020-02-14
      • 2011-01-10
      • 2016-07-01
      • 2013-08-08
      相关资源
      最近更新 更多