递归的思想很简单(把规模大的、较难解决的问题——>规模较小的、易解决的同一类型问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。)但是深入进去就很懵。
1.为了递归地求解问题,必须处理两种情况:
(1)基础情况,在这种情况下我们可以直接求解问题。
例如汉诺塔问题中,当盘子个数为1时,我们直接将盘子从第一根柱子移到第三根(第二根看作备用柱子);也就是说值为1为一种基础情况。
(2)递归情况,在这种情况下我们必须依据更容易的子问题来求解问题。(更容易指它们必须更接近基础情况。例如汉诺塔问题中,依据移动 n-1 张圆盘这个更容易的问题求解移动n张盘子的问题。)
用下面的话再来解释一下:
1)用递归去解决问题时,例如F(n)的问题,转化为F(n-1)的问题,且这两者的处理模式一样——>使对象之间有规律的递增或递
减;(递归情况)
2)必定要有一个明确的结束递归的条件(基础情况),否则递归将会无止境地进行下去,直到耗尽系统资源。
原文链接:https://blog.csdn.net/ggxxkkll/article/details/7524056+https://www.cnblogs.com/Shinea_SYR/p/9655181.html
2.递归的调度机制
(1)当程序执行到一个方法时,就会开辟一个独立的空间(栈);
(2)每个空间的数据(局部变量)都是独立的;
3.使用递归来设计归并排序
其递归思想:(1)如果只有一个数字要排序,则不做任何事;(2)否则,将数字分成两组,递归排序每个组,使有序后归并到一个有序数组中。
归并排序使分治算法的一个实例。(分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。)
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = new int[] { 1, 2, 5, 2, 4, 6, 8, 10 };
arr = mergerHelper(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
// merge改进 1,2,5,2,4,6,8,10
public static int[] mergerHelper(int[] data, int bottom, int top) {
if (bottom == top) {
return new int[] { data[bottom] };// 重合,只有单个元素的数组,直接返回
} else {
int midpoint = (top + bottom) / 2;
return merge(mergerHelper(data, bottom, midpoint), mergerHelper(data, midpoint + 1, top));
}
}
public static int[] merge(int[] a, int[] b) {
int[] result = new int[a.length + b.length];
int i = 0;// 跟踪a数组的下标
int j = 0;// 跟踪b数组的下标
for (int k = 0; k < result.length; k++) {
if (j == b.length || (i < a.length && a[i] <= b[j])) {
result[k] = a[i++];
} else {
result[k] = b[j++];
}
}
return result;
}
}
在对较大的数组排序时,归并排序更快一些。
3.快速排序的递归实现
快速排序是另一种分治排序算法。下面是快速排序的计划
(1)如果有一个或较少的数字要排序,则不做任何事情。
(2)否则,把区域分成较小数字和较大数字两部分,把较小的数字移到左边,较大的移到右边。递归的对每个区域进行排序。
算法实现:首先随意选一个元素(第一个或最后一个较好)作为基准(pivot),</=pivot的数字看成较小数字,>pivot的数字看作较大的数字。该算法维持4个区域:小的数、大的数、未检查的数、包含基准的区域。最后一个动作是把基准pivot交换进小的数和大的数之间的位置。时间复杂度:O(n log n)
代码在我快速排序那一博客里面。
4.避免递归:每当我们调用一个方法时,就不得不把一个帧压到调用栈上。这花费时间和内存。
方法:尾部递归
LinkedList类的get()方法
//原来的递归方法
public Object get(int index) {
return getHelper(index, front);
}
public E getHelper(int index, ListNode <E> node){
if(index==0) {
return node.getItem();
} else {
return getHelper(index-1, node.getNext());
}
}
将其转换为一种迭代算法
public E getHelper(int index, ListNode <E> node) {
while(true) {
if(index==0) {
return node.getItem();
} else {
index--;
node = node.getNext();
}
}
我没有怎么看懂,但是在这种情况下后面的效率会高一点。
5.动态规划
例如Fibonacci数,为了避免一些重复计算,我们用动态规划(用数组来存放以前计算过的值,当我们为n的每个值计算F(n)时,我们可以查找任何更小的n值)。
这个方法的运行时间时n 的线性函数。
public static int fibo(int n) {
int[] f = new int[n + 1];
f[0] =1;
f[1] =1;
for(int i =2; i <=n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
用动态规划把以前计算的值存储在数组中,以避免冗余。
递归允许设计强大、优雅的算法,但它会为调用栈用光时间和空间。虽然这种情况不总是可能发生,但是有时可以通过消除递归来提高效率。可以把尾部递归算法(在该算法中,递归调用是最后一步)轻松地转换成一个循环。如果算法只是返回一个值(与修改现有的数据结构相对),就可以通过把以前调用的结果存储在一张表中来避免冗余的计算。这一种技术称为动态规划。
相关术语:
分治法(divide and conquer): 一种算法,其工作方式是把数据分成几块,递归地来能各个块,然后重新合并解。归并排序和快速排序都是分治算法。
动态规划(dynamic progranming): 用于提高做冗余工作的递归算法效率的技术。它会把子问题的解存储起来,使得可以查找而不是重新计算它们。
辅助方法(helper method):一种方法,通常是受保护的方法,用在递归算法中。递归的辅助方法通常需要一些额外的参数, 来指定正在处理的子问题。
原位(in place):一 种排序方法,用于在原数组内部把元素移来移去,而不用创建新的数据结构。
迭代(iterative): 一种算法,使用循环而不是递归。
递归方程(recurrence): 依据函数自身来定义一个函数的方程。用于分析递归算法的运行时间。
递归树(recursion tree):用于求解递归方程的技术,其工作方法是:反复用其递归定义的成分代替函数。
尾部递归( tail recursive):一种递归算法, 让递归调用成为返回前的最后一步。
end.