递归
栈结构 层层调用 层层返回
设计递归有三个步骤
- 找重复
- 找变化
- 找边界
public static void main(String[] args) {
f(10);
}
//注意死循环
static void f(int i) {
if (i ==0) {
return;
}
//调用自身
f(i-1);
}
n的阶乘
public static void main(String[] args) {
System.out.println(f(4));
}
/*
* f(n):求n的阶乘 -- > f(n-1)求n-1的阶乘
* 1. 找重复 n*(n-1)的阶乘,求n-1的阶乘是原问题的重复(规模更小)--子问题
2. 找变化 变化的量应该作为参数
3. 找边界 出口
*/
static int f(int n) {
if(n==1) {
return 1;
}
return n * f(n-1);
}
打印 i 到 j (委托思想 偷懒思想 我只做一部分(尽可能小的一部分) 剩下部分 我交给其他人做 )
public static void main(String[] args) {
f(10,20);
}
static void f(int i,int j) {
if(i>j){
return;
}
System.out.println(i);
f(i+1,j);
}
对arr的所有元素求和 (Array 数组)
public static void main(String[] args) {
int res = f(new int[] {1,2,3,4,5},0);
System.out.println(res);
}
static int f(int[] arr,int beign) {//这个变化当中去找参数 加参数是设计递归的一个难点 很多人切完蛋糕的一小块 不知道该怎么写 这时候你要想想是不是缺参数
//begin是那个变化量 不停地往前推进 arr是一个常量
if(beign == arr.length-1) { //当begin 等于数组最后一个的下标 就不用再求和 就直接返回那个值即可
return arr[beign];
}
return arr[beign] + f(arr,beign+1);
}
>>> 15
翻转字符串
public static void main(String[] args) {
System.out.println(f("abcd",3)); // a b c d 反转 从后面切一刀 d + (a b c)以后同理 当然也可以从前面切一刀 但是好像没有变化的值了
}
static String f(String src,int end) {
if(end == 0) {
return ""+src.charAt(end);//""+是为了转为String类型
}
return src.charAt(end)+f(src,end-1);
}
>>>dcba
重复中找变化 ,变化中找重复
斐波那契数列
首先斐波那契数列 是这样的一个数列 1 1 2 3 5 8 13
每一项都是前两项的和 求第N项斐波那契数 这个N是第n项 本身无法参与运算 不像我们阶乘那个
这个也符合我们拆解成更小规模的子任务 ,但是这个地方要拆解成两个子任务
一个小弟去求n-1 一个小弟去求n-2 我只负责把他们合起来
这是递归的一种变体
递归包括:
分解为:直接量+小规模子问题
也可以分解为:多个(两个及两个以上)小规模子问题
这个问题 需要拆 和 凑
划一刀 解决一部分 再划一刀 再解决一部分 去拆去凑
public static void main(String[] args) {
System.out.println(f(5));
}
static int f(int n) {
if(n==1||n ==2) {
return 1;
}
return f(n-1)+f(n-2);
}
>>>5
画出递归示意图 你会发现很有意思的事情
之前 那些 比如求10的阶乘 他们是单路径 没有分支 一条路径走下来
但是我们做斐波那契数列时 开叉了
这个叫做递归解答树
这个是 每个点都要开叉
你会发现 有大量重复的求解 以后我们去优化
调用过程 比如 求解f(6)时 f5 和f4是同时进行的吗 显然不是
是先左子树 再右子树 返回来 再求兄弟(也是先求兄弟的左子树 再求兄弟的右子树)
最大公约数
辗转相除法
m % n等于0时 n是最大公约数,m%n 余数是k 则接下来 n%k 再进行判断
如果要写成公式则
f(m,n) = f(n,m%n)
public static void main(String[] args) {
System.out.println(f(33,10));
}
static int f(int m,int n) {
if(n==0) {
return m;
}
return f(n,m%n);
}
总而言之 你能找到问题的演变 推进的方式 本质上来讲都可以写成递推公式
只要在递推范围中再越缩越小 也就是在问题的规模在 越减越小 就是成功的递推设计
插入排序改递归
对数组的0~倒数第一个 排序
等价于:
对数组的0~倒数第二个元素,这部分排序
然后把最后一个元素插入到这个有序的部分中
public static void main(String[] args) {
int[] arr = new int[]{3,2,4,1};
f(arr,3);
for(int i =0;i<=arr.length-1;i++) {
System.out.println(arr[i]+" ");
}
}
static void f(int[]arr,int k) {
if(k ==0) {
return;
}
//对前k-1部分进行排序
f(arr,k-1);
//对位置k的元素插入到前面的部分
int x = arr[k];//假设是3241 保存数组序列号为k(这里是3)的值是1
int index = k-1;//保存下一个要比较的序列号k-1(这里是2)
while(index>-1&&x-1 因为1要和序列号为0的进行比较 比较后满足条件 arr[1]=arr[0] 后减1 index就变为-1了
arr[index+1] = arr[index];
index--;
}
arr[index+1] = x;//注意这个坑 index+1 因为最后符合条件前多减了一次 ,index =-1此时是要把1插入到数组中 所以 index+1 为0
}
>>>1 2 3 4
总结
-
找重复
- 方法1找到一种划分方法
- 方法2找到递推公式或者等价转换
都是父问题转化为求解子问题
找变化的量
变化的量通常要作为参数
如果找变化的量困难 要不是参数不够 要不就没找到隐含的参数找出口‘
所有的循环语句一定能改成递归 有的简单 有的难
汉诺塔
1-N从A移到B,C作为辅助 B为最终的目标柱子
等价于:
1、1~N-1从A移动到C B作为辅助
2、把N从A移动到B
3、1-N-1从C移动到B,A作为辅助
其中 每部 都直接是结果,不表示过程
难点就是N从A到B后 就可以把B作为空柱子 不用再管N 问题也就等价
最难想的就是转换柱子的角色 柱子的角色在变 把柱子作为参数就可以了
除了规模在变化以外 从哪到哪 以谁为辅助(柱子角色) 都在变化
public static void main(String[] args) {
f(3,"A","B","C");
}
/**
* 将N个盘子从source移动到target的路径打印
* @param N 初始的N个从小到大的盘中,N是最大的编号
* @param from 原始柱子
* @param to 目标柱子
* @param help 辅助柱子
*/
static void f(int N,String from,String to,String help) {
if(N==1){
System.out.println("move "+N+" from "+from+" to "+to);
return;
}
f(N-1,from,help,to);//先把前N-1个盘子挪到辅助空间去
System.out.println("move "+N+" from "+from+" to "+to);//N可以顺利到达目标柱子
f(N-1,help,to,from);//让N-1个盘子回到目的空间去
//注意调用方法,除了数值越变越小,这三个柱子的角色 也在互相变化 谁为参考点 谁为目标 它在变化
}
>>>move 1 from A to B
>>>move 2 from A to C
>>>move 1 from B to C
>>>move 3 from A to B
>>>move 1 from C to A
>>>move 2 from C to B
>>>move 1 from A to B