缓存思想在算法设计中的应用

1.问题引入

我们先看一下简单的斐波那契数列的递归算法。百度百科中对该问题是这样定义的:

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(3)=2,F(n)=F(n-1)+F(n-2)(n>=4,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

直接根据递推关系式,我们可以设计常规的递归算法:

fibonacci(n)
if n=1 return 1
if n=2 return 2
if n>2 return fibonacci(n-1)+fibonacci(n-2)

在这个方法中包含了大量重复的计算,比如在计算fibonacci(n)时需要计算一次fibonacci(n-2)的值,在计算fibonacci(n-1)时仍然要计算一次fibonacci(n-2)的值,问题的规模和计算的次数呈现如下的关系:

问题规模为n时子问题的计算次数
问题规模 计算次数
1和2 count(3)+count(4)
3 count(4)+count(5)
4 count(5)+count(6)
5 count(6)+count(7)
n-3 count(n-1)+count(n-2)=3
n-2 count(n-2)=2
n-1 count(n-1)=1

可以发现问题规模n的计算次数形成了一种“倒序”排列的斐波那契额数列,解决问题的所需要的计算次数和原问题呈现出相同的规模,执行效率会特别的慢。

事实上,我们可以使用一个数组缓存下已经计算的子问题的值,当子问题被再次用到时,直接使用缓存的值即可,避免重新计算,从而提升效率。

初始化数组 A[1..n]={1,1,0,0,0...0}
fibonacci(n,A)
if n=1 return A[1]
if n=2 return A[2]
if n>2 
    A[n]=(A[n-1]=0?fabonacci(n-1):A[n-1])+(A[n-2]=0?fabonacci(n-2):A[n-2])
    return A[n]

 借助数组做数据缓存中间结果值,可以大大提升算法的运行效率。当然,对于斐波那契数列本身还有很多更好的优化方法,比如使用迭代进行一趟循环,或者借助斐波那契的矩阵乘法定义进行优化等。我们这里只是用这个例子来说明缓存在算法优化中的应用。

2. 思想分析

现在我们把关注点回到缓存思想上来。从对斐波那契数列的优化过程中可以看到,缓存是典型的以空间换取时间的策略。在计算机的设计与程序设计过程中,缓存是一种经常用到的思想。

  • 在池化技术中,比如数据库连接池,线程池等,为了避免大对象重复创建造成的资源浪费,将连接对象缓存进池里重复使用以提升效率。
  • 在计算机的体系结构中,为了调节CPU和内存之间的速度差异,使用二级缓存的方式,根据8020原则,对经常使用到数据做缓存,从而提升系统的运行效率。
  • 在分布式系统的设计中,将数据缓存到离用户最近的位置以解决大数据场景下热点数据的访问性能问题。
  • 在数据库的访问上,为了避免对硬盘的重复查询,使用Redis、Ehcache和Memcached等开源工具对热点查询数据缓存到内存中。
  • 在动态规划的算法中,我们依然可以看到缓存的策略,比如在求解最长公共子序列时,使用了一个二维数组来对子问题的最优解做缓存。

综上比较,缓存思想的应用可以分成两个方面:一是,在速度相差比较大的软件/硬件之间,用以协调二者的速度差异,提升整体的吞吐率,比如计算机系统中的高速缓冲存储器;二是,对计算资源消耗较大的中间结果做缓存,以避免重复计算,比如数据库连接池中对连接对象的缓存避免重复对对象的创建,以及上述我们在优化斐波那契数列时对子问题中间结果的缓存等。

猜你喜欢

转载自blog.csdn.net/xucr1111/article/details/83990069