Performance 有没有办法通过记住子节点来加速递归?
比如说,, 请看计算第n个斐波那契数的代码: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)被计算了很多次。有没有办法将其存储在内存中,以便快速检索,从而提高递归的速度 我正在寻找一种可以用于几乎所有问题的通用技术。尝试使用映射,
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]