数据结构之递归

本篇是数据结构与算法之美学习笔记

递归在计算机科学中指一种通过将重复问题分解为同列子问题来解决问题的方法。

递归是一种常见的算法或者编程技巧。很多数据结构和算法的编码实现都会使用到递归,比附DFS深度搜索,前中后序二叉树遍历等等。

递归需要满足三个条件

1.一个问题的解可以分成几个解。

子问题就是数据规模更小的的问题

2.这个问题和分解之后的子问题,除了数据规模不同,解决思路是一样的。

3.存在递归的终止条件

如果没有终止条件就成了无限循环了。

如何写一个递归呢?最关键的两部就是写出递推公式,和找到终止条件。

例子一:求阶乘

int f(int n)  
{  
    if(n==0)//递归边界  
        return 1;  
    return n*f(n-1);//递归公式  
}  

例子二:假如有n个台阶,每次可以跨一个或者两个台阶,请问走着n个台阶有多少种走法?

根据第一步的走法可以分成两类:第一步走了一个台阶和第一步走了两个台阶。所以n个台阶的走法就等于西安欧一阶后n-1个台阶的走法加上先走两阶后n-2个台阶的走法。

int f(int n){
  if(n == 1) return 1;//递归边界  
  if(n == 2) return 2;//递归边界  
  return f(n-1) + f(n-2) //递归公式  
}

写递归代码,关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后在推敲终止条件,最后将递推公式和终止条件翻译成代码。

递归代码要警惕堆栈溢出

在实际开发中,写递归代码经常会遇到堆栈溢出的情况,后果非常严重。为什么会造成堆栈溢出呢?怎么可以防止堆栈溢出?

我们知道,函数的调用会使用栈来保存临时变量,每调用一个函数,都会讲临时变量封装成栈帧亚茹内存中,等函数执行完成返回时才出栈。系统或虚拟机的栈控件一般都不大。如果递归求解的数据很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险了。

我们可以通过在代码中限制递归效用的最大深度的方式来解决这个问题,比如在递归深度超过1000之后就不往下递归了,直接返回错误

不过这种方法并不能完全解决问题,因为最大允许的递归深度跟当前的线程剩的栈的空间大小有关,事先无法计算,如果实时计算代码过于复杂。所以如果深度太深,就不要用递归了。

递归代码需警惕重复计算

比如上面的第二个例子加入n为6我们分解开的话

f(6)->f(5)->f(4)->f(3)->f(2)
f(6)->f(5)->f(4)->f(3)->f(1)
f(6)->f(5)->f(4)->f(2)
f(6)->f(5)->f(3)->f(2)
f(6)->f(5)->f(3)->f(1)
f(6)->f(4)->f(3)->f(2)
f(6)->f(4)->f(3)->f(1)
f(6)->f(4)->f(2)

从上面可以看到f(3)被计算了很多次。

为了避免重复计算,我们可以通过一个数据结构(比如散列表)来保存已经求结果的f(k)。当递归调用到f(k)的时候,先看一下是否求解过了,如果是直接在删列表中取值返回。

例子二的代码可以改为下面实现

public int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  // hasSolvedList 可以理解成一个 Map,key 是 n,value 是 f(n)
  if (hasSolvedList.containsKey(n)) {
    return hasSovledList.get(n);
  }
  
  int ret = f(n-1) + f(n-2);
  hasSovledList.put(n, ret);
  return ret;
}

递归调试,对于比较浅的递归,可以使用单步追踪的方法调试,对于比较深的,可以通过打印日志的方式来调试。

你可能感兴趣的:(数据结构与算法)