普通递归,记忆递归,动态规划,斐波那契数列的理解js

走台阶算法实现(理解算法)


文章会分析算法的特点,以及实现过程。帮助理解不同算法之间时间复杂度和空间复杂度的区别

一、程序要求

	  假设有n个台阶,一次只能上1个台阶或2个台阶,请问走到n个台阶有几种走法

二、程序思路

 例:假如有三个台阶,有以下三种走法:111 12 21

    一个(1种)  1        
    二个(2种)  11 2
    三个(3种)  111 12 21
    四个(5种)  1111 112 121 211 22

    爬n个台阶
    要么从n-1个爬1阶直接到n阶
    要么从n-2个爬2阶直接到n阶
    因此爬n阶的走法=爬n-1个的走法+爬n-2个的走法
    即f(n)=f(n-1)+f(n-2)

    f(3)=f(2)+f(1)

    f(2)=2
    f(1)=1	  

为了检测算法正确性 函数一律输入20,只要结果为10946即正确

一、普通递归

1.普通递归
      特点:
      但是由于f(n)=f(n-1)+f(n-2)有两个递归(即f(n-1)和f(n-2)),每个执行n次(取极限),导致时间复杂度变为 O(2^n)  
      而递归的深度为n,即空间复杂度是为O(n)

      当n的值过大时,耗费时间过长 
function NclimbStairs(n){
  //阶梯数为1或者2时,走法数等于阶梯数
  if(n==1 || n==2) return n


  return NclimbStairs(n-1)+NclimbStairs(n-2)
}


//这里我们用console.time去获取函数执行时间,去对比算法的时间复杂度
console.time('N');
console.log('普通递归:'+NclimbStairs(20));
console.timeEnd('N')

二、记忆递归

2.记忆递归   

    特点:一个递归,一个n+1长度的数组,时间复杂度O(n),空间复杂度O(n)
    
    原理:
      在递归的时候,我们做了很多重复的运算
      例如:f(5)=f(4)+f(3) f4=f(3)+f(2)    f3=f(2)+f(1)    可以发现f(3),f(2)执行了两次,即递归两次。
      实际我们只需要执行f(1),f(2)...f(n)  即 n 次

      因此记忆递归就是把执行过的结果用数组保存起来,以减少空间复杂度   

    
    为什么是n+1个长度数组解释:
      因为我们的存储形式是 例如:arr[1]=fn(1)
      而数组的序号是从0起始的
      所以0-n有n+1个数
function climbStairsMemo(n){

  //阶梯数为1或者2时,走法数等于阶梯数
  let arr=[] 	//定义一个数组来保存阶梯的走法
  arr[1]=1,arr[2]=2

  function climbStairs(n){ 
  	//n为1或2,arr里面已经保存过了,直接返回即可
    if(n==1 || n==2) return arr[n]
    let result=arr[n]
    //当数组的第n位保存过数据,那就不进行递归,直接返回
    if(result!==undefined){
      return result
    }else{
    	 //没保存过就进行递归
      result=climbStairs(n-1)+climbStairs(n-2)
      arr[n]=result
    }

    return result
  }
  climbStairs(n)	//调用递归

  return arr[n]
 
}


//这里我们用console.time去获取函数执行时间,去对比算法的时间复杂度
console.time('Memo');
console.log('记忆递归:'+climbStairsMemo(20));
console.timeEnd('Memo')


三、动态规划

  3.动态规划法  
    特点:一个循环,一个n+1长度的数组,空间复杂度O(n)  时间复杂度O(n)

    原理:沿用了记忆递归保存结果的特性,但是运算的方式发生了改变
          根据表达式f(n)=f(n-1)+f(n-2),在外面知道fn(1)=1和f(2)=2的条件下
          我们可以得出fn(3)=f(2)+f(1),当我们得出f(3)可进行保存,然后就可以利用f(2)和f(3)求出f(4)
          类比操作,我们求到f(n)即可求出结果

function climbStairsAc(n){

  //阶梯数为1或者2时,走法数等于阶梯数
	let arr=[]	//定义一个数组来保存阶梯的走法
  arr[1]=1,arr[2]=2
  

  //由于arr[1]和arr[2]的值已经有了,i需要从3开始
  for(var i=3;i<=n;i++){
    arr[i]=arr[i-1]+arr[i-2]
  }
  
  return arr[n]
}


//这里我们用console.time去获取函数执行时间,去对比算法的时间复杂度
console.time('AC');
console.log('动态规划:'+climbStairsAc(20));
console.timeEnd('AC')

四、斐波那契数

  4.斐波那契数列   
  	特点:一个循环,只有三个变量  时间复杂度O(n) 空间复杂度O(1)

	  原理:
	    在动态规划,我们在运算过程实际只用到了三个变量,即arr[i],arr[i-1],arr[i-2] 
	    即两个变量进行运算,一个变量存放结果
	
	    在f(3)=f(2)+f(1)中,我们通过f(1)和f(2)求出了f(3)的结果
	    而进行下一步运算时,我们是用f(2)和f(3)求出f(4)
	    这也就意味着,在下一步运算中f(1)是不需要的
	
	    假设我们有三个变量a,b,c
	    先用a存f(1) 再用b存f(2)
	    c存f(3),f(3)=f(2)+f(1)
	    那么下一步运算中a就不用存f(1)了
	
	    然后开始把f(2)给a,再把f(3)给b,即可求出f(4)   (为什么先把f(2)给a:因为f(2)的值不先给a,就会被f(3)的值覆盖)
	
	    运行到最后返回b的值即可
	      解释:
	        因为变量需要先计算,再赋值
	        所以最后的操作一定是赋值操作
	        即b=c
function climbStairsFb(n){

  let a=1,b=2,c
  

  //由于arr[1]和arr[2]的值已经有了,arr[i]需要从3开始
  for(var i=3;i<=n;i++){
    c=a+b
    a=b
    b=c
  }
  
  return b
}


//这里我们用console.time去获取函数执行时间,去对比算法的时间复杂度
console.time('Fb');
console.log('斐波那契数列:'+climbStairsFb(20));
console.timeEnd('Fb')

五、总结

以下是上面四种算法运行的时间,可对时间复杂度做参考

在这里插入图片描述

对于时间复杂度

	可以看出,普通递归的运行时间最长的。因为它时间复杂度是为O(n^2)。
	而记忆递归,动态规划,斐波那契数列的运行时间基本相差不大。因为它们都是O(n).

对于空间复杂度

	递归算法,记忆递归,动态规划的空间复杂度相同,都是O(n)	
	其中记忆递归的因为也是递归调用,递归次数过多会不停的开辟函数执行栈而导致溢栈现象。
	
	斐波那契数列对动态规划进行了优化,使得其空间复杂度为O(1)

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...