【问题标题】:Recursive base representation递归基表示
【发布时间】:2016-02-27 01:18:52
【问题描述】:

我很高兴在 Python 中重新学习递归,虽然基础很容易,但似乎有一点我只是失去了递归解决问题的能力。

例如下面的问题。

编写一个递归函数,它有两个参数,n,一个以 10 为底的正整数,b,一个 2 到 9 之间的整数。该函数返回数字 n 的基 b 表示。数字的底数 b 表示使用数字 0,..,b-1,数字的位置表示底数的幂。

>>> base(5,3)  # write 5 in base 3
'12'

>>> base(887,7)  # write 887 in base 7
'2405'

我在不使用递归的情况下解决了问题。

def base(n, b):
    if n == 0:
        return [0]
    digits = []
    while n:
        digits.append(int(n % b))
        n //= b
    return digits[::-1]

如果有人愿意指导我递归地解决这个问题,将不胜感激。

另外,学习递归思考以一个接一个地练习问题的最佳方法是什么?我习惯于让新概念立即为我点击,我用递归撞到这堵砖墙的事实让我有些担心。

【问题讨论】:

  • “虽然这确实返回了正确的答案,但效率低下” - 是什么让您认为您的迭代解决方案效率低下?似乎没有什么明显的不足。严格来说,有一些算法具有更好的渐近运行时间,但它们需要的背景知识比你合理预期的要复杂得多,而且只有当涉及的数字达到几百位数时,它们才会变得有价值。
  • 另外,请记住,递归函数的性能通常很差,因为它们的开销很大
  • 等等,Python 3?您应该使用//= 而不是/= 进行楼层划分。也许这就是您认为它效率低下的原因;如果你使用真正的除法,你得到的结果是错误的。
  • @user2357112 啊啊啊。谢谢!这解决了所有零在答案之前返回的问题(这就是为什么我认为它效率低下)

标签: python python-3.x recursion base


【解决方案1】:

递归程序有两个基本部分:

  1. 您必须知道何时停止!

  2. 您必须能够用同一问题的较小版本的解决方案来表达问题。

让我们看看你的“问题”:

您想要生成一个表示n 的字符串,该字符串以b 为基数。

一些快速的经验法则:如果问题涉及“整数”,则可能的停止点为零。如果它涉及“字符串”,则可能的停止点是一个空字符串。如果它涉及像列表这样的复杂数据结构,则可能的停止点是空数据结构。 (这并不总是正确的。但对于课堂作业来说总是正确的。;-)

无论如何,您已经想到使用n 作为最重要的变量,并测试为零。

我将建议 是错误的。 (只是因为...)

让我们尝试一个稍微不同的终止条件:n < b。在这种情况下,你知道你可以产生一个数字。

所以:

if n < b:
    return str(n)

完成后,我们能做什么?好吧,您的其余代码几乎都在:

def base(n, b):
    if n < b: return str(n)
    return base(n // b, b) + str(n % b)

【讨论】:

  • 非常有帮助,我之前没有注意到终止条件的问题。我会问你同样的问题,就像我之前对我的问题的回答一样。您如何决定何时以迭代方式递归地解决问题?似乎递归问题解决要短得多且直截了当,但据我了解,它并不总是最有效的方法。
  • 大多数语言中的递归都受到堆栈深度的限制。如果你有一种优化“尾递归”的语言,你可以做你喜欢的事。但是大多数语言都没有,所以如果我能“看到尽头”,我倾向于避免递归。例如,像这样的问题有一个明显的循环转换。但是像遍历树这样的事情并没有那么明显的转换(没有实现我自己的堆栈等)。所以当终止条件不是很明显时我会递归,当我可以看到如何终止时循环。
【解决方案2】:

我主要修改了您的(已经相当不错的)解决方案。递归的想法是将解决方案分解为更小的子问题。

在这种情况下,我们首先要获取个位的数字,这很简单:只需 n%b。之后,我们需要弄清楚如何将其余数字转换为基数 b。这就是我们递归执行的部分,直到剩下的数字为 0,此时我们就完成了。

   def base_recur(n,b):
        if n == 0:
            return []
        return  base_recur(n//b, b) + [n%b]

输出

   base_recur(887,7)
   [2, 4, 0, 5]

   base(5,3)
   [1, 2]

如果你想要一个字符串表示,你可以用join包装结果

"".join(str(dig) for dig in base_recur(887,7))
'2405'

或者你可以只定义递归函数来本地返回一个字符串

   def base_recur(n,b):
        if n == 0:
            return ''
        return  base_recur(n//b, b) + str(n%b)

【讨论】:

  • 在没有TypeError 的情况下,在函数内部获取字符串表示的最佳方法是什么?
【解决方案3】:

在进行递归时,更有效的形式是使用tail recursion,它在每一步都进行一些计算并携带一个累加器,在最后一步你在累加器中得到答案,因此你不需要从递归调用返回的过程中需要额外的工作,如果你可以这样做很容易将其更改为循环,反之亦然,这就是你在 Haskell 中必须做的事情,例如,如果你需要一个类似循环的计算你以尾递归的形式进行。

现在这个问题可以这样解决

BASE='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' #until base 36, because why not :)

def base(n,b,acc=None):
    if acc is None:  # I need a accumulator, if I don't get one I provide one
        acc=[]
    if n < b:
        acc.append(BASE[n])
        return "".join(reversed(acc))
    else:
        n,d = divmod(n,b)      # n//b and n%b at once 
        acc.append(BASE[d])  
        return base(n,b,acc)   

(我使用列表作为累加器,因为在 python 中字符串是不可变的,所以任何更改它们的操作都会复制它,所以我使用列表)

测试

>>> base(0,16)
'0'
>>> base(42,16)
'2A'
>>> base(5,3)
'12'
>>> base(420,25)
'GK'
>>> base(25,25)
'10'
>>> base(42,2)
'101010'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 2019-11-08
    • 1970-01-01
    • 2010-12-29
    • 1970-01-01
    • 2020-06-20
    相关资源
    最近更新 更多