【问题标题】:Finding Power Using Recursion使用递归寻找力量
【发布时间】:2019-09-25 04:38:38
【问题描述】:

在 Python 3 中

def power(base,exponent):
    if exponent == 1:
        return base
    return base * power(base, exponent - 1)

我没有考虑过极端情况(指数

为什么我们不用上面写的代码来代替使用Divide and Conquer Technique计算的代码,这段代码看起来更简单易懂?这段代码是不是效率较低?

【问题讨论】:

  • 另一个版本减少了递归调用,但是这个更容易理解。我个人认为没有必要优化递归电源实现,因为它也可以以迭代方式编写。
  • 都是递归,调用栈决定优化算法。上述算法是交互式递归,具有 o(n) 复杂度,同时另一个具有 o(log(n)) 时间复杂度。所以人们会更喜欢第二个。

标签: python recursion


【解决方案1】:

这些是使用您的代码计算 2^8 所采取的步骤:

power(2,8)=
2*power(2,7)=
2*2*power(2,6)=
2*2*2*power(2,5)=
2*2*2*2*power(2,4)=
2*2*2*2*2*power(2,3)=
2*2*2*2*2*2*power(2,2)=
2*2*2*2*2*2*2*power(2,1)=

这些是用分而治之计算 2^8 所采取的步骤:

power(2,8)=
power(2,4)**2=
power(2,2)**2**2=
power(2,1)**2**2**2=

如您所见,您的方法需要 O(n) 步,而分而治之需要 O(lg(n)) 步,这要快得多。

如果您关心速度,对此类问题使用递归绝不是一个好主意,因为正如您所见,迭代方法(函数式语言中的尾递归)通常要快得多,在本示例中,它是您所见速度的两倍基准,至于分而治之的方法,除非您使用的幂小于 ~22,否则您应该始终使用它。

以下是一些基准:

代码:

def r_power(base, exponent):  # recursive credits to OP
    if exponent == 1:
        return base
    return base * r_power(base, exponent - 1)


def lindc_power(x, y):  # linear divide and conquer, credits to Smitha Dinesh Semwal
    if y == 0:
        return 1
    elif int(y % 2) == 0:
        return lindc_power(x, int(y / 2)) * lindc_power(x, int(y / 2))
    else:
        return x * lindc_power(x, int(y / 2)) * lindc_power(x, int(y / 2))


def lgdc_power(x, y):  # logarithmic divide and conquer
    if y == 0:
        return 1
    elif int(y % 2) == 0:
        return lgdc_power(x, int(y / 2)) ** 2
    else:
        return x * lgdc_power(x, int(y / 2)) ** 2


def i_power(base, exponent):  # iterative, credits to Yugandhar Chaudhari
    acc = 1
    while True:
        if exponent == 0:
            return acc
        base, exponent, acc = base, exponent - 1, acc * base

结果:

|---------------------------------------------------------------------|
| base | power   | recursive | iterative | linear dc | logarithmic dc |
|---------------------------------------------------------------------|
| 2    | 10      | 1.27 µs   | 746 ns    | 8.99 µs   | 2.33 µs        |
| 2    | 22      | 2.96 µs   | 1.58 µs   | 18.6 µs   | 2.95 µs        |
| 2    | 100     | 15.1 µs   | 8.31 µs   | 75.3 µs   | 4.14 µs        |
| 2    | 500     | 96.7 µs   | 48.9 µs   | 312 µs    | 5.69 µs        |
| 2    | 1000    | 424 µs    | 178 µs    | 1.3 ms    | 6.58 µs        |
| 2    | 1500    | 201 µs    | 108 µs    | 620 µs    | 7.89 µs        |
| 2    | 2000    | 435 µs    | 242 µs    | 1.23 ms   | 8.15 µs        |
| 2    | 3000    | error     | 409 µs    | 2.49 ms   | 10.3 µs        |
| 2    | 6000    | error     | 1.13 ms   | 5.01 ms   | 17.6 µs        |
| 2    | 10000   | error     | 2.64 ms   | 9.84 ms   | 25.2 µs        |
| 2    | 20000   | error     | 8.74 ms   | 19.9 ms   | 45.7 µs        |
| 2    | 100000  | error     | 161 ms    | 82.4 ms   | 249 µs         |
| 2    | 200000  | error     | 626 ms    | 159 ms    | 532 µs         |
| 2    | 1000000 | error     | 15 s      | 651 ms    | 3.23 ms        |
|---------------------------------------------------------------------|

我的最大递归深度是 3000。

【讨论】:

  • @hilberts_drinking_problem 是的,我刚刚意识到我没有重命名内部调用:)) 我没有看到你的 cmets,因为我正在重做基准测试。
  • @hilberts_drinking_problem 我现在要增加递归深度来做更多的测试。
  • dc_power 的这种实现是线性的(这在问题链接的网页中明确指出),因为它执行两个递归调用而不是一个。难怪它不快。
  • @GZ0 python 不缓存结果吗?
  • 我所知道的大多数语言都不会这样做,除非被明确告知,因为没有办法知道函数调用是否会产生任何副作用。在 Python 中,您需要 @functools.lru_cache
【解决方案2】:

是的,如果它不是尾递归的话,效率可能会降低。递归会为过多的堆栈帧创建可能会耗尽内存的堆栈帧。正确的方法是使用尾递归来表达它。您只需要使用一个变量来存储结果acc 在这里它将评估一个变量中的结果,而不是进行递归函数调用

def power2(base,exponent,acc):
    if exponent == 0:
        return acc
    return power2(base,exponent-1,acc*base)

print(power2(10,2,1)) #start acc from 1 initially

但是python没有尾部优化所以这是在这里使用尾部优化的方式

def power2(base,exponent,acc):
    while True:
        if exponent == 0:
            return acc
        base,exponent,acc = base,exponent-1,acc*base

print(power2(10,100,1))

【讨论】:

  • python 没有尾部优化,这会产生最大的递归错误
  • @yukashimahuksay 是的,尝试第二个实现,它不会
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多