C/C++ 使用 memoization 优化递归 避免重复计算

C/C++ 使用 memoization 优化算法

以常见的斐波那契数列计算为例:

#include <stdio.h>
 
#define COUNT_TIMES 10
 
int fib(int n)
{
    if (n == 0 || n == 1)
    {
        return 1;
    }
    else
    {
        return fib(n - 2) + fib(n - 1);
    }
}
 
int main()
{
    int i;
    for (i = 0; i < COUNT_TIMES; i++)
        printf("fib %d\n", fib(i));
}

输出:

fib 1
fib 1
fib 2
fib 3
fib 5
fib 8
fib 13
fib 21
fib 34
fib 55

实际上,我们来看看其中的计算次数

#include <stdio.h>
 
#define COUNT_TIMES 10
 
int count;
 
int fib(int n)
{
    if (n == 0 || n == 1)
    {
        return 1;
    }
    else
    {
        count++;
        return fib(n - 2) + fib(n - 1);
    }
}
 
int main()
{
    int i, *mem;
 
    for (i = 0; i < COUNT_TIMES; i++)
    {
        printf("n %d 结果 %2d 计算次数 %d\n", i, fib(i), count);
        count = 0;
    }
}

结果:

n 0 结果  1 计算次数 0
n 1 结果  1 计算次数 0
n 2 结果  2 计算次数 1
n 3 结果  3 计算次数 2
n 4 结果  5 计算次数 4
n 5 结果  8 计算次数 7
n 6 结果 13 计算次数 12
n 7 结果 21 计算次数 20
n 8 结果 34 计算次数 33
n 9 结果 55 计算次数 54

我们发现实际上计算的次数跟其结果相当,计算 n 的斐波那契数列其计算量就是 fib(n) - 1 次了。想想也是醉了。

那么让我们使用 memoization 来优化一下:

#include <stdio.h>
#include <stdlib.h>
 
#define COUNT_TIMES 10
 
int count;
 
int fib(int n, int *mem)
{
    // 如果没有缓存结果则进行计算,并把结果加入到缓存中
    if (mem[n] == -1)
    {
        mem[n] = fib(n - 1, mem) + fib(n - 2, mem);
        // 统计计算次数
        count++;
    }
    // 返回缓存结果
    return mem[n];
}
 
int main()
{
    int i, j, *mem;
    for (i = 0; i < COUNT_TIMES; i++)
    {
        // 申请一块内存来缓存结果
        mem = (int *)malloc(sizeof(int) * COUNT_TIMES);
        // 初始化其中的结果
        mem[0] = mem[1] = 1;
        for (j = 2; j < COUNT_TIMES; j++)
            mem[j] = -1;
 
        // 调用计算
        printf("n %d 结果 %2d 计算次数 %d\n", i, fib(i, mem), count);
 
        count = 0; // 计算次数清零
        free(mem); // 释放用来缓存的内存
    }
}

优化之后,可以发现时间复杂度很轻松的变成 O(n) 了

n 0 结果  1 计算次数 0
n 1 结果  1 计算次数 0
n 2 结果  2 计算次数 1
n 3 结果  3 计算次数 2
n 4 结果  5 计算次数 3
n 5 结果  8 计算次数 4
n 6 结果 13 计算次数 5
n 7 结果 21 计算次数 6
n 8 结果 34 计算次数 7
n 9 结果 55 计算次数 8

优化之后,当 n = 15,速度大概是原版的1000倍,当 n = 27 速度大概是原来的 10000 倍了。应该说重复计算的计算量越大使用 memoization 获得的效果就越明显,不过实际应用中要考虑到其所消耗的内存是否值得,也就是看一个性价比吧。

最后去掉注释简单封装一下。

#include <stdio.h>
#include <stdlib.h>
 
#define COUNT_TIMES 10
 
int * init_mem() {
    int i, *mem;
    mem = (int *)malloc(sizeof(int) * COUNT_TIMES);
    mem[0] = mem[1] = 1;
    for (i = 2; i < COUNT_TIMES; i++)
        mem[i] = -1;
    return mem;
}
 
int fib(int n, int *mem)
{
    if (mem[n] == -1)
        mem[n] = fib(n - 1, mem) + fib(n - 2, mem);
    return mem[n];
}
 
int main()
{
    int i, *mem;
 
    for (i = 0; i < COUNT_TIMES; i++)
    {
        mem = init_mem();
        printf("fib %d\n", fib(i, mem));
        free(mem);
    }
}

使用Memoization,以避免递归重复计算

考虑Fibonacci(斐波那契)问题;

Fibonacci问题是可以通过简单的递归方法来解决:

int fib ( n )
 { 
    if ( n == 0 || n == 1 ) { 
        return 1; 
    } 
    else { 
        return fib( n - 2 ) + fib ( n - 1 ); 
    } 
}

注:在这里,我们考虑Fibonacci 系列从1开始,因此,该系列看起来:1,1,2,3,5,8,...

注意:从递归树,我们计算fib(3)函数2次,fib(2)函数3次。这是相同函数的重复计算。如果n非常大,fib

这个简单的技术叫做Memoization,可以被用在递归,加强计算速度。

fibonacci 函数Memoization的代码,应该是下面的这个样子:

int calc_fib ( int n )
 { 
    int val[ n ] , i;
    for ( i = 0; i <=n; i++ ) { 
        val[ i ] = -1;      // Value of the first n + 1 terms of the fibonacci terms set to -1 
    } 
    val[ 0 ] = 1;   // Value of fib ( 0 ) is set to 1 
    val[ 1 ] = 1;    // Value of fib ( 1 ) is set to 1 
    return fib( n , val ); 
}
 int fib( int n , int* value ) 
{
     if ( value[ n ] != -1 ) { 
        return value[ n ];              // Using memoization 
    } 
    else { 
        value[ n ] = fib( n - 2 , value ) + fib ( n - 1 , value );          // Computing the fibonacci term
     }
     return value[ n ];                // Returning the value 
}

上面代码的红色部分,不知道为什么可以那么声明,在标准C和C++中数组都不可以那样声明吧,可能是使用编译器进行了扩展。

下面是我用C++写的:

#include <iostream>
#include <algorithm>
#include <iterator>
using namespace std;
int fib(int n,int val[])
{
    if(val[n]!=-1)
        return val[n];
    else
    {
        val[n]=fib(n-1,val)+fib(n-2,val);
        return val[n];
    }
}
void cal_fib(int n)
{
    int *val=new int[n+1];
    for(int i=0;i<=n;i++)
        val[i]=-1;
    val[0]=0;
    val[1]=1;
    fib(n,val);
    copy(val,val+n+1,ostream_iterator<int>(cout," "));
    cout<<endl;
    delete[] val;
}
int main()
{
    for(int i=3;i<=15;i++)
        cal_fib(i);
}

输出的结果如下:

JS用memoization优化递归调用

一. 前言

memoization, 这个词见过几次, 脑海中对它的印象, 是用来优化递归调用的(我知道, 绝不仅于此), 即便如此, 我依然认为, 它不是一种方法, 而应该是一种思路或思想, 特在此记录一点现有的理解, 以后逐步增加...

二. 应用

网上一搜, 发现大都以计算裴波拉契数为例, 咱也不搞特殊化:
     

 // 常规代码
 function fib(n)  {
      if (n < 2)  { return n; }
      return fib(n-1) + fib(n-2);
 }

 
分析: 这段代码的执行效率非常低, 原因就在于, 需要反复调用函数自生, 且每次调用都有很多重复计算,  很明显, n越大, 调用次数越多.

// 优化后的代码
var mFib = function() {
     var cache = [1, 1]; // 裴波拉契数的前两个数为1, 1
     return function(m) {
         var len = cache.length, i = len;
         // 如果m大于cache的长度, 则说明m对应的裴波拉契数不存在, 需要计算
         if (m > len)  {
               // 当i等于m的时候, 小于i之前的裴波拉契数已经计算过了, 可以直接使用
               // 有的例子用的for循环, 但while循环更快
               while (i <= m)  {
                    // 裴波拉契数的特点, 后一个数等于前两个数之和
                    // 把计算结果缓存起来, 避免再次计算
                    cache[i] = cache[i - 2] + cache[i - 1]; 
                    i++;
               }
          }
          // 当裴波拉契数存在时, 直接使用
          return cache[m - 1];
     }
}();

分析: 该方法的思路是, 将计算过的结果缓存起来, 这样就可以减少很多重复计算, 从而提高执行效率.

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索算法
, 递归
, int
优化
如何避免递归、递归调用避免栈溢出、递归避免死循环、memoization、memoization技术,以便于您获取更多的相关知识。

时间: 2024-09-19 09:28:42

C/C++ 使用 memoization 优化递归 避免重复计算的相关文章

分众传媒回应浑水质疑:不存在重复计算问题

分众传媒于今天凌晨对做空机构浑水(Muddy Waters)再次发布的质疑报告及时进行了回应,称所有的LCD广告屏幕数都经过两个独立的第三方机构核对,不存在重复计算的问题. 此前浑水于昨日晚间再度发布报告质疑分众传媒.浑水在报告中指出,分众传媒号称的18多万块LCD广告牌中有超过3万块仅为普通平面海报,占总量的近六分之一,因此浑水质疑分众财务报告数字的准确性. 浑水昨晚对分众显示屏数量的质疑,致分众早盘一度下跌,最低跌至22.18美元:午后,分众迅速回应浑水质疑报告.受此影响,该股午后逐步上涨尾

《从问题到程序:用Python学编程和计算》——2.8 重复计算和循环

2.8 重复计算和循环 在前面几节,我们首先看到如何通过语句的顺序组合构造最简单的程序,这种程序是直线型程序,就是简单的一系列语句.这样的程序中只有一条执行路径(一种可能执行方式):Python解释器顺序执行程序里的语句,每个语句执行一次,当语句序列中最后一条语句的执行结束时,整个程序的执行就结束了. 增加了if复合语句,能写出的程序更多,程序的形式也更丰富,其中出现了选择和分支.这样得到的程序可称为分支程序.在分支程序里,每条基本语句最多执行一次,如果实际条件导致的执行没进入某个分支,该分支里

优化-递归是用栈来实现的,栈里面具体都存放了什么数据?

问题描述 递归是用栈来实现的,栈里面具体都存放了什么数据? 同上,今天看到尾递归可以实现递归的优化,无论多深的递归栈都不会出现溢出(原文如此).我在想栈里不是会存放每次递归函数的地址吗?为什么无论多深栈都不会溢出? 解决方案 一个对自己本身的递归 尾调用,就叫做尾递归.这里尾调用的"尾"字,是指运行时需要执行的最后一个动作.不是简单的语法字面上的最后一个语句. 尾递归实际执行的是迭代的计算过程.线性递归函数必须满足以下两个基本属性:*必须清晰无误地解决基的情况.*每一个递归的调用,必须

SEO优化之如何处理重复链接

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 在我们进行网站优化的时候我们经常会遇到处理各种各样的重复页面,造成这些原因有很多,但大多是是由于程序错误而产生,所以我们在做站的时候选择一套成熟的程序很有必要的,这会让你在做站的时候省去很多麻烦.天我们就结合我个人处理姐妹祛痘网的重复页面的经验来个大家讲讲如何处理重复页面. 301跳转重复链接 每个网站都难免会经历结构或者内容的调整.我在做姐

软考 递归式时间复杂度计算详解

递归算法的时间复杂度分析 在算法分析中,当一个算法中包含递归调用时,其时间复杂度的分析会转化为一个递归方程求解.实际上,这个问题是数学上求解渐近阶的问题,而递归方程的形式多种多样,其求解方法也是不一而足,比较常用的有以下四种方法: 方法一:代换法 代换法主要需要以下两个步骤 1.  猜答案,不需要完全猜出来,不需要知道常熟系数的准确值,而只需要猜出它的形式,比如猜一个递归式的时间复杂度大概是O(n2),即它的运行时间应该是一个常熟乘以n2,可能还会有一些低阶项.   方法二:递归树法 递归树法主

打印菱形以及斐波纳契数列的几种解法介绍

1.编写程序,打印*菱形推出第i行要打印的空白个数及*号个数,用for循环依次打印各行 复制代码 代码如下: #include<stdio.h> //总共要打印2*n-1行,逐行打印 void print1(int n) { int i,j; for(i=1;i<=n;i++){//打印1至n行 for(j=1;j<=n-i;j++)//打印n-i个空格 printf(" "); for(j=1;j<=2*i-1;j++)//打印2*i-1个*号 prin

打印菱形以及斐波纳契数列的几种解法介绍_C 语言

1.编写程序,打印*菱形推出第i行要打印的空白个数及*号个数,用for循环依次打印各行 复制代码 代码如下: #include<stdio.h>//总共要打印2*n-1行,逐行打印void print1(int n){ int i,j; for(i=1;i<=n;i++){//打印1至n行  for(j=1;j<=n-i;j++)//打印n-i个空格      printf(" ");  for(j=1;j<=2*i-1;j++)//打印2*i-1个*号 

0-1背包

问题描述:       给定n种物品和一背包,物品i的重量是wi,其价值为vi,背包的容量为C.问应如何选择装入背包的物品(物品不能分割),使得装入背包中物品的总价值最大?  抽象描述:      x[n]:表示物品的选择,x[i]=1表示选择放进物品i到背包中.               问题分析:          1.抽象之后背包问题转换为找到一个最优的数组,x1,x2,.....,xn的0-1序列.                    2.假设最优解的序列为x1,x2,.....,x

性能优化:memoization

memoization适用于递归计算场景,例如 fibonacci 数值 的计算. 'use strict'; let n = process.env.N || 50; console.log('process $', process.pid);console.log('fibonacci recursive version with n = ', n);let count = 0;function fibonacci(n) { count++; //console.log(count); if