1.什么是递归
递归是一种编程模式,用于一个任务可以被分割为多个相似的更简单的任务的场景。或者用于一个任务可以被简化为一个容易的行为上更简单的任务变体。或者像我们随后会看到的,用来处理特定类型的数据结构。
当一个函数解决一个任务时,在该过程中它可以调用很多其他函数。那么当 一个函数调用自身时 ,就称其为递归。
简单点:就是函数调用自身——>递:传递 归:回归
递归的表现:函数调用自己
递归的用处:将大型复杂问题化解为若干小问题进行求解
递归的好处:代码量少
递归的弊端:占空间,函数是基于栈内存运行的
递归的要素:前进段、边界条件、返回段
通俗点——我们可以把” 递归 “比喻成 “查字典 “,当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词。
可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。
2.递归的用法
1)如何用递归求1-100的和?
①你需要了解这个函数要干嘛?(不需要管代码是什么,你得清楚这个函数是干嘛用的)
假设f(n)=1+2+3+···+99+100——>求前100个数的和
//计算前1-100的和
public static int f(int n){
}
②寻找递归结束的条件
所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么
可以看到,f(1) = 1
所以当n=1的时候,返回一个值
//当n为1时,f(n)=1
int f(int n){
if(n == 1){
return 1;
}
}
有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗?
当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件。
③找出函数的等价关系式(我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变)
当你觉得f(n)这个范围太大了,我们可以写为——>f(n) = f(n-1)+n
代码如下
public static int f(int n){//第一步
if(n==1){//第二步
return 1;
}else{//第三步
return f(n-1)+n;
}
}
2)求n的阶乘:
第一步——>找功能
int f(int n){
}
第二步——>结束条件
我们都知道,1的阶乘还是1,所以f(1) =1,当输入为0时,f(0)=1
int f(int n){
if(n==1||n==0){
return 1;
}
}
第三步——>找出等价关系式
可以得出,f(n)=f(n-1)*n,代码如下
// 算 n 的阶乘
int f(int n){
if(n==1||n==0){
return n;
}
return f(n-1) * n;
}
3)斐波那契数列:斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34....,即第一项 f(1) = 1,第二项 f(2) = 1.....,第 n 项目
为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少
第一步——>递归函数的功能是什么
假设f(n)的功能是求第n项的值,代码如下
int f(int n){
}
第二步——>递归结束的条件
从题中可以看到,当n=1或2,都是1 ,所以f(1)=f(2)=1,可以把n=2或n=1作为递归结束条件,代码如下
int f(int n){
if(n <= 2){
return 1;
}
}
第三步——>找出等价关系式
f(n) = f(n-1)+f(n-2),后一项的值时前两项的和
int f(int n){//1.写函数
// 2.先写递归结束条件
if(n <= 2){
return 1;
}
// 3.接着写等价关系式
return f(n-1) + f(n - 2);
}
3.分治算法
当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。
比如 二分查找法:用递归写二分查找法,边界值就是找到当前值或查找范围为空,否则每次查找范围缩小一半。代码如下:
public static int search(int[] array, int key, int low, int high) {//定义一个数组,一个需要找的值key,一个最小值的角标low,最大值的角标high
int mid = (high - low) / 2 + low;
if (key == array[mid]) {// 查找值等于当前值,返回数组下标
return mid;
} else if (low > high) {// 找不到查找值,返回-1
return -1;
} else {
if (key < array[mid]) {// 查找值比当前值小
return search(array, key, low, mid - 1);
}
if (key > array[mid]) {// 查找值比当前值大
return search(array, key, mid + 1, high);
}
}
return -1;
}
4.汉诺塔问题
由来
汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
解题思路:试想一下,如果只有两个盘子,盘子从小到大我们以数字命名,两个盘子上面就是盘子1,下面是盘子2,那么我们只需要将盘子1先移动到B塔座上,然后将盘子2移动到C塔座,最后将盘子1移动到C塔座上。即完成2个盘子从A到C的移动。
但如果有三个盘子,那么我们将盘子1放到C塔座,盘子2放到B塔座,在将C塔座的盘子1放到B塔座上,然后将A塔座的盘子3放到C塔座上,然后将B塔座的盘子1放到A塔座,将B塔座的盘子2放到C塔座,最后将A塔座的盘子1放到C塔座上。
如果有四个,五个 ,n个盘子呢?
这时候递归的思想就很好解决这样的问题了,当只有两个盘子的时候,我们只需要将B塔座作为中介,将盘子1先放到中介塔座B上,然后将盘子2放到目标塔座C上,最后将中介塔座B上的盘子放到目标塔座C上即可。我们不管有几个盘子,就把他们当成两个,(N-1)~1是一个,第一个是一个,解决方法如下:
①、先将A塔座的第(N-1)~1个盘子看成是一个盘子,放到中介塔座B上,然后将第N个盘子放到目标塔座C上。
②、然后A塔座为空,看成是中介塔座,B塔座这时候有N-1个盘子,将第(N-2)~1个盘子看成是一个盘子,放到中介塔座A上,然后将B塔座的第(N-1)号盘子放到目标塔座C上。
③、这时候A塔座上有(N-2)个盘子,B塔座为空,又将B塔座视为中介塔座,重复①,②步骤,直到所有盘子都放到目标塔座C上结束。
代码如下——:
/**
* 汉诺塔问题
*
* @param dish
* 盘子个数(也表示名称)
* @param from
* 初始塔座
* @param temp
* 中介塔座
* @param to
* 目标塔座
*/
public static void move(int dish, String from, String temp, String to) {
if (dish == 1)
System.out.println(dish+"——>" + from + "——>" + to);
} else {
move(dish - 1, from, to, temp);// A为初始塔座,B为目标塔座,C为中介塔座
System.out.println("将盘子" + dish + "从塔座" + from + "移动到目标塔座" + to);
move(dish - 1, temp, from, to);// B为初始塔座,C为目标塔座,A为中介塔座
}
}