可记忆函数-递归优化


先说一下Fibonacci数列:一个 Fibonacci数字是前2个 Fibonacci 数字之和, Fibonacci 最前面的2个数是0和1。
不多说,大家都知道递归算法:

var fibonacci = function(n){
return n<2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
结果:

console.info(fibonacci(1));//1
console.info(fibonacci(2));//1
console.info(fibonacci(3));//2
console.info(fibonacci(4));//3
console.info(fibonacci(5));//5
console.info(fibonacci(6));//8
这是最典型的 Fibonacci实现,但该函数在计算 fibonacci(6)的时候,它计算了25次。在总的6次取值中,共计算了58次。

在《 JavaScript语言精粹》一书中介绍了 下面函数来提高计算效率,该函数通过记忆存储结果,而大大减少计算量,总的 6次取值总共仅计算了16次。

var memorizer = function(memo,fundamental){
var shell = function(n){
var result = memo[n];
if(typeof result !== 'number'){
result = fundamental(shell,n);
memo[n] = result;
}
return result;
};
return shell;
}
var fibonacci = memorizer([0,1],function(shell,n){
return shell(n-1)+shell(n-2);
});
结果:

console.info(fibonacci(1));//1
console.info(fibonacci(2));//1
console.info(fibonacci(3));//2
console.info(fibonacci(4));//3
console.info(fibonacci(5));//5
console.info(fibonacci(6));//8

个人理解:
     首先 memorizer返回了shell,shell是一个function,shell的参数为n,即memorizer返回了一个function(n){..}的函数
    可以理解为下面的语句:

fibonacci = function(n){
var result = memo[n];
if(typeof result !== 'number'){
//fibonacci就是memorizer返回的那个shell,shell和fibonacci其实是同一个东西
//所以fundamental函数的参数shell就是fibonacci
result = fundamental(shell,n);
memo[n] = result;
}
return result;
}
    然后我们分析上面函数中的memo和fundamental
    通过原本的代码可以看出,其中memo作为函数的入参,可以认为一个数组指针,指向memorizer的第一个参数:[0,1]
     var fibonacci = memorizer ([0,1],function(shell,n){return shell(n-1)+shell(n-2);});

    fundamental 作为函数的另一入参,可以认 为是一个function指针,指向memorizer的第二个参数,即匿名function:
     var fibonacci = memorizer([0,1],function(shell,n){return shell(n-1)+shell(n-2);});

因为fibonacci就是memorizer返回的shell, 所以 shell和fibonacci其实是同一个东西,上面fundamental函数的参数shell即是fibonacci
fibonacci = function(n){
    var result = memo[n];//memo为数组[0,1]
    if(typeof result !== 'number'){
	result = fundamental(fibonacci,n);// fundamental(shell,n) shell用fibonacci替换
	memo[n] = result;
    }
    return result;
}

即得到以下代码:


调整后得到基本的带记忆功能的Fibonacci函数:
fibonacci = function(n){
    var memo = [0,1];
    var result = memo[n];//memo为数组
    if(typeof result !== 'number'){
        //result = fundamental(fibonacci,n);
        //fundamental(shell,n){return shell(n-1)+shell(n-2);}, 直接把执行语句拿出来
        result = fibonacci(n-1)+fibonacci(n-2);
        memo[n] = result;
    }
    return result;
}

测试 结果:

console.info(fibonacci(1));//1
console.info(fibonacci(2));//1
console.info(fibonacci(3));//2
console.info(fibonacci(4));//3
console.info(fibonacci(5));//5
console.info(fibonacci(6));//8

总结:memorizer介绍了一种加速程序计算的优化技术-memorization(记忆),通过缓存结果,使得函数避免重复验算已被处理的输入。
简单说来, memorizer 返回的factorial函数会记住之前操作的结果,如果记忆数组里存在所需的数值,就从数组里拿,如果数组中不存在,才去计算, 从而避免了无谓的运算。
PS:这里另一个重点在于memorizer函数将这种形式抽象出来,将其一般化,使我们只用提供基本的公式,就能生产出其他函数的函数。
例如:要产生可记忆的阶乘函数,只需要提供基本的阶乘公式:

var memorizer = function(memo,fundamental){
var shell = function(n){
var result = memo[n];
if(typeof result !== 'number'){
result = fundamental(shell,n);
memo[n] = result;
}
return result;
};
return shell;
}
var factorial = memoizer ([ 1 , 1 ], function ( shell , n ){ return n * shell ( n - 1 );});

相关阅读:《JavaScript语言精粹》( 蝴蝶书 )第四章15节


你可能感兴趣的:(算法)