求解递推公式的有效算法

时间:2014-11-30 07:21:47

标签: performance algorithm recursion sequences

我给出了一个公式f(n),其中定义了f(n),对于所有非负整数,如下:

f(0) = 1

f(1) = 1

f(2) = 2

f(2n) = f(n) + f(n + 1) + n (for n > 1)

f(2n + 1) = f(n - 1) + f(n) + 1 (for n >= 1)

我的目标是为任何给定的数字s找到最大的n,其中f(n)= s。如果没有这样的n返回None。 s最高可达10 ^ 25。

我有一个使用递归和动态编程的强力解决方案,但两者都不够高效。哪些概念可以帮助我找到解决这个问题的有效方法?

4 个答案:

答案 0 :(得分:1)

我想添加一点复杂度分析并估计f(n)的大小。

如果你看一个f(n)的递归调用,你注意到输入n基本上除以2,然后再调用f(n)两倍,其中一个调用总是有一个偶数和一个人有一个奇怪的输入 因此,调用树基本上是二叉树,其中特定深度k上的一半节点总是提供约n / 2 k + 1 的加数。树的深度是log 2(n)。

因此f(n)的值总共约为Θ(n / 2·log 2(n))。

注意:这适用于偶数和奇数输入,但对于偶数输入,该值约为额外的加数n / 2更大。 (我使用Θ-表示法不必考虑一些常量)。

现在的复杂性:

天真蛮力

要计算f(n),你必须调用f(n)Θ(2 log 2(n))=Θ(n)次。
因此,如果你想计算f(n)的值直到你达到s(或注意到没有n与f(n)= s)你必须计算f(n)s·log 2(s)次数,是总Θ(s²⋅log(s))。

动态编程

如果存储f(n)的每个结果,计算f(n)的时间减少到Θ(1)(但它需要更多的内存)。因此总时间复杂度将降低到Θ(s⋅log(s))。

注意:由于我们知道所有n的f(n)≤f(n + 2),因此您不必对f(n)的值进行排序并进行二分搜索。

使用二进制搜索

算法(输入为s):

  1. 设置l = 1r = s
  2. 设置n =(l + r)/ 2并将其四舍五入为下一个偶数
  3. 计算val = f(n)。
  4. 如果val == s则返回n。
  5. 如果val<然后设置l = n
    否则设r = n。
  6. 转到2
  7. 如果你找到了解决方案,那很好。如果不是:再次尝试,但在第2步中转到奇数。如果这也没有返回解决方案,则根本不存在解决方案。

    这将使您获得二元搜索的Θ(log(s))和每次计算f(n)的Θ(s),因此总得到Θ(s⋅log(s))。< / p>

    正如您所看到的,这与动态编程解决方案具有相同的复杂性,但您无需保存任何内容。

    注意:r = s不能作为初始上限保留所有s。但是,如果s足够大,它就会成立。要保存,您可以更改算法:
    首先检查,如果f(s)&lt;秒。如果没有,你可以设置l = s和r = 2s(如果它必须是奇数,则设置2s + 1)。

答案 1 :(得分:0)

  1. 你能计算f(x)的值,其中x从0到MAX_SIZE只有一次吗?

    我的意思是:按DP计算值。

    f(0)= 1
    f(1)= 1
    f(2)= 2
    f(3)= 3
    f(4)= 7
    f(5)= 4
    ...... ... f(MAX_SIZE)= ???

  2. 如果第1步是非法的,请退出。否则,将值从小到大排序 如1,1,2,3,4,7,......
    现在你可以在O(log(MAX_SIZE))时间内找到是否满足f(n)= s。

答案 2 :(得分:0)

2n2n+1的每次迭代中的这种递归都是递增值,因此如果在任何时刻您的值都会大于s,那么您可以停止算法。< / p>

要制作有效的算法,你必须找到或好的公式,它将计算值,或者在小循环中进行,这将比你的递归更多,更多,更有效。您的递归通常为O(2 ^ n),其中循环为O(n)。 这就是循环的外观:

int[] values = new int[1000];
values[0] = 1;
values[1] = 1;
values[2] = 2;
for (int i = 3; i < values.length /2 - 1; i++) {
    values[2 * i] = values[i] + values[i + 1] + i;
    values[2 * i + 1] = values[i - 1] + values[i] + 1;
}

在这个循环中添加可能破坏成功的条件。

答案 3 :(得分:0)

不幸的是,你没有提到你的算法应该有多快。也许你需要找到一些非常巧妙的重写你的公式以使其足够快,在这种情况下你可能想在数学论坛上发布这个问题。

根据Master定理,公式的运行时间为f(2n + 1)的O(n)和f(2n)的O(n log n),因为:

T_even(n)= 2 * T(n / 2)+ n / 2

T_odd(n)= 2 * T(n / 2)+ 1

因此整个公式的运行时间为O(n log n)。

因此,如果n是问题的答案,那么这个算法可以在约。 O(n ^ 2 log n),因为你必须大约执行n次公式。

通过存储以前的结果,你可以更快一点,但当然,这是对内存的权衡。

以下是Python中的这种解决方案。

D = {}

def f(n):
    if n in D:
        return D[n]
    if n == 0 or n == 1:
        return 1
    if n == 2:
        return 2
    m = n // 2
    if n % 2 == 0:
        # f(2n) = f(n) + f(n + 1) + n (for n > 1)
        y = f(m) + f(m + 1) + m
    else:
        # f(2n + 1) = f(n - 1) + f(n) + 1 (for n >= 1)
        y = f(m - 1) + f(m) + 1
    D[n] = y
    return y

def find(s):
    n = 0
    y = 0
    even_sol = None
    while y < s:
        y = f(n)
        if y == s:
            even_sol = n
            break
        n += 2
    n = 1
    y = 0
    odd_sol = None
    while y < s:
        y = f(n)
        if y == s:
            odd_sol = n
            break
        n += 2
    print(s,even_sol,odd_sol)

find(9992)