Performance 有没有办法通过记住子节点来加速递归?

Performance 有没有办法通过记住子节点来加速递归?,performance,recursion,Performance,Recursion,比如说,, 请看计算第n个斐波那契数的代码: fib(int n) { if(n==0 || n==1) return 1; return fib(n-1) + fib(n-2); } 这段代码的问题是,它将为任何大于15的数字生成堆栈溢出错误(在大多数计算机中) 假设我们正在计算fib(10)。在这个过程中,假设fib(5)被计算了很多次。有没有办法将其存储在内存中,以便快速检索,从而提高递归的速度 我正在寻找一种可以用于几乎所有问题的通用技术。尝试使用映射,

比如说,, 请看计算第n个斐波那契数的代码:

fib(int n)
{
    if(n==0 || n==1)
        return 1;
    return fib(n-1) + fib(n-2);
}
这段代码的问题是,它将为任何大于15的数字生成堆栈溢出错误(在大多数计算机中)

假设我们正在计算fib(10)。在这个过程中,假设fib(5)被计算了很多次。有没有办法将其存储在内存中,以便快速检索,从而提高递归的速度


我正在寻找一种可以用于几乎所有问题的通用技术。

尝试使用映射,n是键,其对应的斐波那契数是值

@保罗

谢谢你的信息。我不知道。从你提到的:

这种保存值的技术 已经计算过的被称为 回忆录


是的,我已经看过代码(+1)。:)

这是什么语言?它不会在c中溢出任何内容。。。
另外,您可以尝试在堆上创建一个查找表,或者使用映射缓存通常是解决这类问题的好方法。由于斐波那契数是常数,因此计算结果后可以缓存它。一个快速的c/伪代码示例

class fibstorage {


    bool has-result(int n) { return fibresults.contains(n); }
    int get-result(int n) { return fibresult.find(n).value; }
    void add-result(int n, int v) { fibresults.add(n,v); }

    map<int, int>   fibresults;

}


fib(int n ) {
    if(n==0 || n==1)
            return 1;

    if (fibstorage.has-result(n)) {
        return fibstorage.get-result(n-1);
    }

    return ( (fibstorage.has-result(n-1) ? fibstorage.get-result(n-1) : fib(n-1) ) +
             (fibstorage.has-result(n-2) ? fibstorage.get-result(n-2) : fib(n-2) )
           );
}


calcfib(n) {
    v = fib(n);
    fibstorage.add-result(n,v);
}
类存储{
bool有结果(int n){返回fibresults.contains(n);}
int get result(int n){返回fibresult.find(n).value;}
void add result(int n,int v){fibresults.add(n,v);}
map纤维结果;
}
纤维蛋白原(整数n){
如果(n==0 | | n==1)
返回1;
if(fibstorage.has结果(n)){
返回存储,得到结果(n-1);
}
返回((fibstorage.has result(n-1)?fibstorage.get result(n-1):fib(n-1))+
(fibstorage.has结果(n-2)?fibstorage.get结果(n-2):fib(n-2))
);
}
calcfib(n){
v=fib(n);
添加结果(n,v);
}

这将是相当缓慢的,因为每次递归都会导致3次查找,但是这应该说明总体思路

是的,您的见解是正确的。 这就是所谓的。这通常是一种常见的内存运行时权衡

对于fibo,您甚至不需要缓存所有内容:

[编辑] 问题的作者似乎在寻找一种通用的缓存方法,而不是计算斐波那契的方法。搜索维基百科或查看另一张海报的代码可以得到这个答案。这些答案在时间和记忆上是线性的

**这是一个线性时间算法O(n),内存中的常数**

in OCaml:

let rec fibo n = 
    let rec aux = fun
        | 0 -> (1,1)
        | n -> let (cur, prec) = aux (n-1) in (cur+prec, cur)
    let (cur,prec) = aux n in prec;;



in C++:

int fibo(int n) {
    if (n == 0 ) return 1;
    if (n == 1 ) return 1;
    int p = fibo(0);
    int c = fibo(1);
    int buff = 0;
    for (int i=1; i < n; ++i) {
      buff = c;
      c = p+c;
      p = buff;
    };
    return c;
};
因此,您有:

| u(n)    |  = | 1 1 |^(n-1) | u(1) | = | 1 1 |^(n-1) | 1 |
| u(n-1)  |    | 1 0 |       | u(0) |   | 1 0 |       | 1 |
计算矩阵的指数具有对数复杂性。 只要递归地实现这个想法:

M^(0)    = Id
M^(2p+1) = (M^2p) * M
M^(2p)   = (M^p) * (M^p)  // of course don't compute M^p twice here.
你也可以对角化它(不难),你会在它的特征值中找到黄金数和它的共轭,结果会给你一个精确的u(n)的数学公式。它包含了这些特征值的幂,因此复杂性仍然是对数的

Fibo经常被作为一个例子来说明动态规划,但正如您所看到的,它并不真正相关

@约翰: 我认为这和哈希无关

@约翰2:
地图有点普通,你不觉得吗?对于斐波那契的例子,所有的键都是连续的,因此向量是合适的,同样有更快的方法来计算斐波序列,请看我的代码示例。

这就是所谓的记忆化,最近发布了一篇关于记忆化的非常好的文章。它用斐波那契来举例说明。并用C#显示代码。阅读它。

这是故意选择的例子吗?(例如,你想测试的极端情况)

由于它目前是O(1.6^n),我只想确保您只是在寻找关于处理此问题的一般情况(缓存值等)的答案,而不是意外地编写糟糕的代码:D

看看这个具体案例,你可能会有以下几点:

var cache = [];
function fib(n) {
    if (n < 2) return 1;
    if (cache.length > n) return cache[n];
    var result = fib(n - 2) + fib(n - 1);
    cache[n] = result;
    return result;
}

]

如果您使用的语言具有Scheme之类的一流功能,则可以在不更改初始算法的情况下添加记忆:

(define (memoize fn)
  (letrec ((get (lambda (query) '(#f)))
           (set (lambda (query value)
                  (let ((old-get get))
                    (set! get (lambda (q)
                                (if (equal? q query)
                                    (cons #t value)
                                    (old-get q))))))))
    (lambda args
      (let ((val (get args)))
        (if (car val)
            (cdr val)
            (let ((ret (apply fn args)))
              (set args ret)
              ret))))))


(define fib (memoize (lambda (x)
                       (if (< x 2) x
                           (+ (fib (- x 1)) (fib (- x 2)))))))
(定义(记忆fn)
(letrec((get(lambda(query)’(#f)))
(设置(lambda(查询值)
(让(老的得到)
(设置!获取(lambda(q)
(如果(相等?q查询)
(cons#t值)
("""""""""""""""老
(λargs)
(let((val(get args)))
(如果(车辆价值)
(cdr val)
(let((ret(应用fn参数)))
(设置参数ret)
(()())
(定义fib(记忆)(λ(x)
(如果(
第一个块提供一个记忆功能,第二个块是使用该功能的斐波那契序列。现在它有一个O(n)运行时(与没有记忆的算法的O(2^n)相反)

注意:提供的记忆功能使用一系列闭包来查找以前的调用。在最坏的情况下,这可能是O(n)。但是,在这种情况下,所需的值始终位于链的顶部,确保O(1)查找

这段代码的问题是,它将为任何大于15的数字生成堆栈溢出错误(在大多数计算机中)

真的吗?你在用什么电脑?44需要很长时间,但堆栈没有溢出。事实上,在堆栈溢出(Fibbonaci(46))之前,您将获得一个大于整数的值(约40亿个无符号,约20亿个有符号)

这将适用于您想要做的事情(运行速度很快)

类程序
{
public static readonly Dictionary Items=new Dictionary();
静态void Main(字符串[]参数)
{
Console.WriteLine(Fibbonacci(46.ToString());
Console.ReadLine();
}
公共静态整数Fibbonacci(整数)
{
如果(数字==1 | |数字==0)
{
返回1;
}
var minus 2=数字-2;
fibs = 1:1:(zipWith (+) fibs (tail fibs))
fib n = fibs !! n
(define (memoize fn)
  (letrec ((get (lambda (query) '(#f)))
           (set (lambda (query value)
                  (let ((old-get get))
                    (set! get (lambda (q)
                                (if (equal? q query)
                                    (cons #t value)
                                    (old-get q))))))))
    (lambda args
      (let ((val (get args)))
        (if (car val)
            (cdr val)
            (let ((ret (apply fn args)))
              (set args ret)
              ret))))))


(define fib (memoize (lambda (x)
                       (if (< x 2) x
                           (+ (fib (- x 1)) (fib (- x 2)))))))
class Program
{
    public static readonly Dictionary<int,int> Items = new Dictionary<int,int>();
    static void Main(string[] args)
    {
        Console.WriteLine(Fibbonacci(46).ToString());
        Console.ReadLine();
    }

    public static int Fibbonacci(int number)
    {
        if (number == 1 || number == 0)
        {
            return 1;
        }

        var minus2 = number - 2;
        var minus1 = number - 1;

        if (!Items.ContainsKey(minus2))
        {
            Items.Add(minus2, Fibbonacci(minus2));
        }

        if (!Items.ContainsKey(minus1))
        {
            Items.Add(minus1, Fibbonacci(minus1));
        }

        return (Items[minus2] + Items[minus1]);
    }
}
ulong Fib(int n)
{
  ulong fib = 1;  // value of fib(i)
  ulong fib1 = 1; // value of fib(i-1)
  ulong fib2 = 0; // value of fib(i-2)

  for (int i = 0; i < n; i++)
  {
    fib = fib1 + fib2;
    fib2 = fib1;
    fib1 = fib;
  }

  return fib;
}
// your original method
int fib(int n)
{
    if(n==0 || n==1)
        return 1;
    return fib(n-1) + fib(n-2);
}

// with memoization
map<int, int> M = map<int, int>();
int fib(int n)
{
    if(n==0 || n==1)
        return 1;

    // only compute the value for fib(n) if we haven't before
    if(M.count(n) == 0)
        M[n] = fib(n-1) + fib(n-2);

    return M[n];
}
vector<unsigned int> fib_cache;
fib_cache.push_back(1);
fib_cache.push_back(1);

unsigned int fib(unsigned int n) {
    if (fib_cache.size() <= n)
        fib_cache.push_back(fib(n - 1) + fib(n - 2));

    return fib_cache[n];
}
[Serializable]
public class MemoizeAttribute : PostSharp.Laos.OnMethodBoundaryAspect, IEqualityComparer<Object[]>
{
    private Dictionary<Object[], Object> _Cache;

    public MemoizeAttribute()
    {
        _Cache = new Dictionary<object[], object>(this);
    }

    public override void OnEntry(PostSharp.Laos.MethodExecutionEventArgs eventArgs)
    {
        Object[] arguments = eventArgs.GetReadOnlyArgumentArray();
        if (_Cache.ContainsKey(arguments))
        {
            eventArgs.ReturnValue = _Cache[arguments];
            eventArgs.FlowBehavior = FlowBehavior.Return;
        }
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        if (eventArgs.Exception != null)
            return;

        _Cache[eventArgs.GetReadOnlyArgumentArray()] = eventArgs.ReturnValue;
    }

    #region IEqualityComparer<object[]> Members

    public bool Equals(object[] x, object[] y)
    {
        if (Object.ReferenceEquals(x, y))
            return true;

        if (x == null || y == null)
            return false;

        if (x.Length != y.Length)
            return false;

        for (Int32 index = 0, len = x.Length; index < len; index++)
            if (Comparer.Default.Compare(x[index], y[index]) != 0)
                return false;

        return true;
    }

    public int GetHashCode(object[] obj)
    {
        Int32 hash = 23;

        foreach (Object o in obj)
        {
            hash *= 37;
            if (o != null)
                hash += o.GetHashCode();
        }

        return hash;
    }

    #endregion
}
[Memoize]
private Int32 Fibonacci(Int32 n)
{
    if (n <= 1)
        return 1;
    else
        return Fibonacci(n - 2) + Fibonacci(n - 1);
}
 function (parameters)
      body (with recursive calls to calculate result)
      return result
 function (parameters)
      key = serialized parameters to string
      if (cache[key] does not exist)  {
           body (with recursive calls to calculate result)
           cache[key] = result
      }
      return cache[key]
# Compute Fibonacci numbers
sub fib {
      my $n = shift;
      return $n if $n < 2;
      fib($n-1) + fib($n-2);
}
use Memoize;
memoize('fib');
# Rest of the fib function just like the original version.
# Now fib is automagically much faster ;-)
fib[0] = 1;
fib[1] = 1;
fib[n_] := fib[n] = fib[n-1] + fib[n-2]