【问题标题】:Python code to find the max values via recursivePython代码通过递归查找最大值
【发布时间】:2020-08-26 02:39:48
【问题描述】:

我有一个关于在列表中查找最大值的 Python 代码的问题。功能代码如下:

def large(x):
    if len(x) == 1:
        return x.pop(0)
    else:
        temp = x.pop(0)
        previous = large(x)
        if previous >= temp:
            return previous
        else:
            return temp

但在此之前,我尝试过:

def large(x):
    if len(x) == 1:
        return x.pop(0)
    else:
        temp = x.pop(0)
        if large(x) >= temp:
            return large(x)
        else:
            return temp

它会返回错误信息:

<ipython-input-74-dd8676a7c4e6> in large(x)
      3         return x.pop(0)
      4     else:
----> 5         temp = x.pop(0)
      6         if large(x) >= temp:
      7             return large(x)

IndexError: pop from empty list

玩具数据是:

inputlist = [6,1,3,2,3,4,5,6]
large(inputlist)

提前感谢您的帮助。我找不到这个错误的主要原因。至于我,这两个代码是完全一样的。

【问题讨论】:

  • 我假设你不能使用max() 函数?你确定你想要一个可变参数吗?
  • 在第二个版本中,您在同一个堆栈帧中调用 large(x) 两次,一次在 if 中,另一次在 return
  • 考虑使用索引而不是弹出。这将避免偷偷摸摸的突变并且它还将具有更好的性能界限..对于每个递归调用,传递当前索引 + 1。这是一个很好的“模式”,可以递归迭代类似数组数据。
  • 如果我要使用 pop,我会 1) 从 back 弹出以避免糟糕的复杂性转移数据,2) 使用循环而不是递归来保持边-效果关闭。
  • @OneCricketeer 我的目标是应用递归来构建max 函数。

标签: python python-3.x list recursion


【解决方案1】:

问题

if large(x) >= temp:
    return large(x)

是你最终不止一次调用large(x)(因此pop),这是从列表中删除元素。

就个人而言,我更倾向于这种风格,而不是使用像 pop 这样的变异函数。

def large(x):
    if len(x) == 1:
        return x[0]
    remainder = large(x[1:])
    return x[0] if x[0] > remainder else remainder

【讨论】:

  • 如果他必须使用 pop,我建议使用类似 cache=large(x) return cache if cache&gt;temp else temp
  • @xkcdjerry 在“练习”之外,我仍然对改变参数表示谨慎
  • 同意。另外,pop(0) 的性能很差。如果没有明确要求,最好不要使用它。
  • 感谢您的建议,@OneCricketeer。我很好奇为什么要使用余数 = large(x[1:])。我对算法很陌生,根据我的理解,递归将考虑列表中的每个元素。这就是我在这里使用pop 函数的原因。你介意解释更多吗?谢谢。
  • @OneCricketeer 谢谢你的解释。感谢您的时间和耐心。我认为这个问题是由我两次调用large 引起的,这将两次消除列表中的元素。这就是为什么我们应该使用previous = large(x) 而不是直接调用large(x)
【解决方案2】:

与 OneCricketeer 的解决方案相同 但无需在每次递归调用时创建列表切片。 它还处理一个空列表。

def large(x):
    def rec(y):
        try:
            v = next(y)
        except StopIteration:
            return None
        r = rec(y)
        if r is None:
           return v
        return v if v > r else r
    return rec(iter(x))

inputlist = [6,1,3,2,3,4,5,6]
print(large(inputlist))
print(large([]))

产生

6
None

【讨论】:

  • 感谢您的帮助!
【解决方案3】:

这并不能回答为什么原件不正确。相反,它制定了一种“标准模式”,可用于实现许多递归问题。

我想知道如何用索引而不是pop来消除每一轮中的元素数量?

不要“消除”这些元素 :-)

许多递归友好的问题通过减少每一步的范围来运行。这包括找到最大值(或任何可以表示为折叠的操作)、二进制搜索、自上而下的合并排序等。这些问题中的许多问题本身都是使用数组和子问题减少的伪代码表示的通过调整每个递归调用的范围。在 max/binary-search 的情况下,这也避免了对原始对象的任何突变。

因此,递归最大函数可以写成如下。请注意,这种在工作状态中传递的形式是尾调用友好的。虽然我发现这种形式更容易表达某些问题,但在 Python 中并不重要,因为 [C]Python does not support Tail-Call Optimizations^.

def my_max(lst, m=None, i=0):
  # base-case: return result of work
  if len(lst) == i:
    return m
  # Compute max through here
  c = lst[i]
  m = c if m is None or c > m else m
  # Call recursive function increasing the index by 1.
  # This is how the problem advances.
  return my_max(lst, m, i + 1)

上面的例子也使用默认参数而不是辅助方法。这是一种使用递归结果的替代方法——这通常是引入递归函数的方式——以及离散辅助方法。

def my_max(lst):
  # Wrapper can ensure helper pre-conditions.
  # In this case that is a non-Empty list per the base case check.
  if not lst:
    return None
  return my_max_helper(lst, 0)

def my_max_helper(lst, i):
  # base case: last item in list returns itself
  if len(lst) - 1 == i:
    return lst[i]
  c = lst[i]
  m = my_max_helper(lst, i + 1)
  return c if c > m else m

在这两种情况下,都使用临时变量来避免重复表达式;虽然有时只是一种风格选择,但由于避免了额外的pop-mutation的意外副作用,这种一致性可以缓解最初的问题。

上述方法应该使用list 或其他支持O(1) 索引查找的序列来调用。 特别是,“索引”方法不适合,也不适用于生成器对象。还有其他答案涵盖了这一点——请注意避免潜在的列表切片,如 h,*t=ll[1:],这可能会导致性能下降。

^Python 中有一些模块可以通过跳板模拟 TCO。

【讨论】:

  • 感谢您的帮助。我可以问其他问题吗? m = c if m is None or c &gt; m else m 是否等于 if m is None or c&gt;m: return m=c else: return m?您介意解释更多有关辅助方法的信息吗?对于my_max_helper,我想知道我们应该如何定义i 的边界?它会增长到len(lst) 以上吗?谢谢。
【解决方案4】:

由于这是一个递归练习,而不是我们在系统代码中做的事情,我会使用描述性代码而不是高效代码,并执行以下操作:

def largest(array):
    if array:
        head, *tail = array

        if tail and head < (result := largest(tail)):
            return result

        return head

    return None

if __name__ == "__main__":
    from random import choices

    array = choices(range(100), k=10)

    print(array, '->', largest(array))

输出

> python3 test.py
[46, 67, 0, 22, 23, 20, 30, 7, 87, 50] -> 87
> python3 test.py
[83, 77, 61, 53, 7, 65, 68, 43, 44, 47] -> 83
> python3 test.py
[36, 99, 47, 93, 60, 43, 56, 90, 53, 44] -> 99
> 

如果您真的需要提高效率,我建议您安全地这样做。具体来说,不公开带有调用者不应该使用的特殊参数的API,例如:

def my_max(lst, m=None, i=0):

因为他们可以为这些额外的参数提供值,这会使您的代码失败,并最终归咎于您。同上公开调用者可能调用的内部函数,而不是预期的函数:

def my_max(lst, m=None, i=0):

def my_max_helper(lst, i):

意外调用 my_max_helper() 并为命名不佳的 i 参数提供了虚假值。相反,我会考虑嵌套您的函数以避免此类调用错误:

def largest(array):

    def largest_recursive(array, index):
        a = array[index]

        if len(array) - index != 1:
            if (b := largest_recursive(array, index + 1)) > a:
                return b

        return a

    if array:
        return largest_recursive(array, 0)

    return None

【讨论】:

  • 感谢您的帮助。我不太确定我们应该如何在这里实现递归?我对 Python 有点陌生。
  • @QiangSuper 这是递归的...通知largest(tail)
  • 由于在head, *tail = array 中创建了一个新列表,因此具有糟糕的性能界限
  • @user2864740 我对 Python 还是很陌生。我想我应该查一下headtail的功能。哈哈。
猜你喜欢
  • 2012-10-18
  • 1970-01-01
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
相关资源
最近更新 更多