重构左递归无限循环

时间:2019-01-14 17:00:50

标签: python recursion math formula recurrence

当您尝试实现此公式时

enter image description here

使用此代码以直接方式

def P(n, p, limit):
    if n == limit:
        return 1

    if n == -limit:
        return 0

    return p*P(n+1, p, limit) + (1-p)*P(n-1, p, limit)

您肯定会遇到此错误。

  

RecursionError:超过最大递归深度

那么如何使用代码正确实现此类问题呢?是否有用于重构并获得解决方案的技术?还是唯一的办法就是先用数学方法解决该问题,然后才将其输入计算中?

3 个答案:

答案 0 :(得分:3)

要扩展@Prune的建议,可以通过将RHS上的任一术语移到LHS上,将这种重复写成其他两次重复:

  

enter image description here

这些是非循环的,因为每个术语仅取决于之前或之后的术语,而不是取决于


但是,它们不能独立求解,因为这需要了解P 1-1 和P l-1 ,其中l = limit

为方便起见,进行了一些替换:

enter image description here

请注意,由于关系仅包含 linear 项(例如,不包含P n ×P n-1 等),因此所有项在它们的展开中,变量x, y也是线性的:

enter image description here

这些新引入的系数也遵循类似的递归关系:

enter image description here

他们的边界条件是:

enter image description here

现在已经知道边界条件,可以迭代计算系数。仅需从两个重复中使P 1-1 和P l-1 的表达式相等:

enter image description here

求解这对联立方程,得出P 1-1 和P l-1 ,从中可以计算出任何P n (使用先前计算的系数):

  

enter image description here


更新:示例Python实现

# solve a recurrence of the form
# Tn = c1 * Tn-1 + c2 * Tn-2, n >= 0
def solve_recurrence(c1, c2, t1, t2, n):
    if n <= 0: return t2
    if n == 1: return t1
    a, b = t2, t1
    for i in range(1, n):
        a, b = b, c1 * b + c2 * a
    return b

# solve the loop recurrence
def solve_loop(limit, p, n):
    assert(limit > 0 and abs(n) <= limit)
    assert(p > 0 and p < 1)

    # corner cases
    if n == -limit: return 0
    if n ==  limit: return 1

    # constants
    a, c = 1 / p, 1 / (1 - p)
    b, d = 1 - a, 1 - c

    # coefficients at 2l - 1
    e = 2 * limit - 1
    l = solve_recurrence(a, b, 1, 0, e)
    m = solve_recurrence(a, b, 0, 0, e)
    s = solve_recurrence(c, d, 1, 0, e)
    t = solve_recurrence(c, d, 0, 1, e)
    # note that m can just be 0 but was left in for generality

    # solving the simultaneous equations
    # to get P at 1 - l and l - 1
    x = (s * m + t) / (1 - s * l)
    y = (l * t + m) / (1 - l * s)

    # iterate from the closest side to improve precision
    if n < 0:
        return solve_recurrence(a, b, x, 0, limit + n)
    else:
        return solve_recurrence(c, d, y, 1, limit - n)

limit = 10, p = 0.1的实验结果:

n       | P(n)            abs. error
========================================
-10     | 0.0000000E+00   --
-9      | 6.5802107E-19   0.0000000E+00
-8      | 6.5802107E-18   1.7995889E-30
-7      | 5.9879917E-17   1.7995889E-29
-6      | 5.3957728E-16   5.0003921E-28
-5      | 4.8568535E-15   8.1994202E-27
-4      | 4.3712339E-14   8.9993252E-27
-3      | 3.9341171E-13   5.1002066E-25
-2      | 3.5407061E-12   5.9938283E-25
-1      | 3.1866355E-11   5.9339980E-18
 0      | 2.8679726E-10   5.3406100E-17
 1      | 2.5811749E-09   3.3000371E-21
 2      | 2.3230573E-08   2.6999175E-20
 3      | 2.0907516E-07   2.2999591E-19
 4      | 1.8816764E-06   7.9981086E-19
 5      | 1.6935088E-05   1.9989978E-18
 6      | 1.5241579E-04   3.5000757E-16
 7      | 1.3717421E-03   1.5998487E-15
 8      | 1.2345679E-02   3.2000444E-14
 9      | 1.1111111E-01   6.9999562E-14
 10     | 1.0000000E+00   --

注意:

  • 从代码和方程式可以明显看出,重复极限/边界条件可以是任意的。
  • 可以使用诸如Kahan-Neumaier之类的求和技术来提高数值精度。
  • solve_recurrence中使用的迭代方法还存在一个替代的显式公式,如果与库函数结合使用,则可能会产生更准确的结果。

答案 1 :(得分:0)

递归取决于将问题分解为多个部分,每个部分都更接近基本情况。您给出的公式是无限的,因为它取决于同样不确定的系列术语。简单的数学符号不能保证可计算性。

在这种情况下,您必须以非圆形术语重写递归关系。您仍然可以使用递归,但是必须有一个明确的目标。在给定的公式中,P [n]取决于P [n + 1]和P [n-1],每个 直接取决于P [n]。这在算法上是循环的。

您需要从基本案例中提取算法,从头开始并逐步进行。此递归关系的不幸结果之一是,您必须按顺序求解整个范围[-limit,limit]获得所需位置的值。基本情况值会从端点到您有视线的地方不断波动。

没有工具可以为您象征性地解开此问题。最重要的是,请注意基本情况不会出现在等式中。即使您提供了这些作为附加方程式,从这种关系中导出指数级数也超出了当今求解器的分析能力。

答案 2 :(得分:0)

我还有另一个使用符号计算的解决方案。

class X(object):
    """Representing unknown value which will be resolved in future"""
    def __init__(self, value=1.0):
        self.value = value

    def __add__(self, other):
        cls = self.__class__
        return cls(value=self.value+other.value)

    def __sub__(self, other):
        cls = self.__class__
        return cls(value=self.value-other.value)

    def __mul__(self, other):
        cls = self.__class__
        if isinstance(other, cls):
            return cls(value=self.value*other.value)
        elif isinstance(other, numbers.Real):
            return cls(value=self.value*other)
        else:
            raise TypeError(f'Cannot multiply with type {type(other)}')

    def __rmul__(self, other):
        return self.__mul__(other)

我们不是立即获得数值结果,而是依靠这样的事实,即结果值将以某种方式表示为

eq1

,每个中间项将表示为

eq2

这就是为什么我们为对象定义加/减/乘运算。

因此,在代码中具有这种符号的表示形式,我们可以使用与接受的答案类似的方法来解决该方程式(我们从-limitlimit的角度出发,并表示其他未知的术语产品)。达到基点(0状态)时,我们将获得概率与该比率的数值之比,从而使我们可以轻松地计算结果

_memory = []
def fill_memory(n):
    global _memory
    _memory = [(0, X(1))]*(2*n+1)
    _memory[-n] = (0, X(0))
    _memory[n] = (1, X(0))


def resolve(n, p):
    q = 1-p
    for i in reversed(range(n)):
        # resolve from right to center
        prob_next = _memory[i+1]
        prob_prev = _memory[i-i]
        _memory[i] = (
            q*prob_prev[0] + p*prob_next[0],
            q*prob_prev[1] + p*prob_next[1],
        )

        # resolve from left to center
        prob_prev = _memory[-i-1]
        prob_next = _memory[-i+1]
        _memory[-i] = (
            q*prob_prev[0] + p*prob_next[0],
            q*prob_prev[1] + p*prob_next[1],
        )

    # calculate base probability
    solution = _memory[0]
    coef = (X(value=1) - solution[1]).value
    p0 = solution[0] / coef
    return p0

通过这种方法,我获得了正确且非常精确的结果(我使用的是p=0.6N=4(限制))

def main():
    N = 4
    p = 0.6
    fill_memory(N)
    p0 = resolve(N, p)
    print(f'Probability for N={N} steps is {p0*100:.4}%')
    for i in range(2*N+1):
        print(i-N, '->', _memory[i-N])
4 - 83.505%
3 - 77.143%
2 - 69.231%
1 - 60.0%

如您所见,它的结果与数学求解时的结果相同。