先问个问题:递归思想难吗?有的人说,递归不就是自己调用自身嘛,有什么难的;有的人说,我擅长递归,写代码会显得短小精悍。然而总有那么一些人说递归好难,太难理解了。平常我也刷一些算法,有的题大概明白可以用递归,结果揉了半天才写出来。感觉递归下去把我弄懵逼了。
递归难在哪里?递归的思想和我们的正常思维是相反的。我们通常是从上往下的思维,而递归是从下往上的思维。有啥解决办法呢,两个字:练题。记住:我们写算法是通过计算机来执行的,虽然我们不好理解,但是对于计算机而言,这都不是事。。
递归算法解决问题的特点:
- 递归就是方法里调用自身;
- 在使用递归策略时,必须有个明确的递归结束条件,称为递归出口;
- 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序;
- 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。
递归过程中,找好递归的出口是至关重要的。而且每次进行自身调用时通常都要使问题规模有所减少,从而能到满足递归出口的条件。
递归的经典示例
- 示例一:求n得阶乘
问题描述: 给定一个正整数n,求n的阶乘。
递归算法:
public int factorial(int n) {
//递归出口
if(n == 1) {
return 1;
}
//问题的规模n逐渐减小,直到n等于1.
return n * factorial(n - 1);
}
非递归算法:
public int factorial(int n) {
int result = 1;
for(int i = 1; i <=n; i++) {
result *= i;
}
return result;
}
- 示例二:上台阶的所有走法?
问题描述: 有n阶台阶,一个人每次可以走1步或者2步,请问有多少种走法?
递归算法:
public int step(int n) {
//终止条件
if(n == 1)
return 1;
//终止条件
if(n == 2) {
return 2;
}
return step(n -1) + step(n - 2);
}
非递归算法:
public int step(int n) {
//借助于队列实现的,用其他的数据结构也可以的
Queue<Integer> queue = new LinkedList<>();
queue.add(n);
int count = 0;
while(!queue.isEmpty()) {
int value = queue.poll();
if(value == 1)
count += 1;
else if(value == 2) {
count += 2;
}else {
queue.add(value-1);
queue.add(value-2);
}
}
return count;
}
注:其实第二题用动态规划法能有更好地解决方案。等大家了解了动态规划法之后,再想想。。
- 示例三:求链表的所有元素之和
问题描述:有一个链表,其中头结点为head,求所有节点的元素之和。
节点的数据结构如下:
class ListNode{
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
递归算法:
public int nodeSum(ListNode head) {
if(head == null) {
return 0;
}
return head.val + nodeSum(head.next);
}
非递归算法:
public int nodeSum(ListNode head) {
int sum = 0;
ListNode p = head;
while(p != null) {
sum += p.val;
p = p.next;
}
return sum;
}
总结:递归是一种比较好的解决问题的方案,如果能够熟练掌握,算法水平绝对能上一个档次。上面主要给一些简单的使用递归的示例。在大部分的算法题中。递归通常会和分治法、动态规划法等结合使用的。做题时多画些图,有助于理解递归,祝咱们都更进一步!!