• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
百事可爱
一起努力鸭~~~
博客园    首页    新随笔    联系   管理    订阅  订阅
递归

递归

1. 调用机制

递归就是方法自己调用自己,每次调用传入不同的变量

public class DiGui {

	public static void main(String[] args) {	
		test(4);
		  
	}
	 //打印问题
	public static void test(int n) {
		if(n>2) {
			test(n-1);
		}
		System.out.println("n="+ n);
	}
结果:
n=2
n=3
n=4

java虚拟机分为栈,堆,代码区/常量 三部分
代码从main主方法开始执行

  1. 在栈中开辟一个main栈帧1号,在里面调用了test(4),当程序执行到一个方法时,就会开辟一个独立的空间
  2. 在栈中开辟一个新的空间2号中,其中:n=4, if(n>2) test(3);
  3. 又会开辟一个空间3号,其中:n=3, if(n>2) test(2);
  4. 又会开辟一个空间4号,其中:n=2,此时不满足 if(n>2) ,开始往下执行输出语句,n=2; (此处位于栈顶),执行完后,此空间就没有了
  5. 又会重新回到了 空间3号,执行输出语句,n=3;
  6. 又会重新回到了 空间2号,执行输出语句,n=4; 之后就退出程序。
  7. 所以,发现从栈底依次向上开辟空间,先从栈顶依次向下执行,直到栈底。
  8. 还有每个空间中的n是独立的,互相不影响。

2. 规则:(重要)

  1. 当执行一个方法时,就会创建一个新的受保护的空间(栈空间)。
  2. 方法中的局部变量是独立的,不会相互影响。但方法中使用引用类型的变量(比如数组),就会共享该引用类型的变量。
  3. 递归必须向退出递归的条件逼近,否则就会出现无限递归,然后栈溢出,报错java.lang.StackOverflowError
  4. 当一个方法执行完毕,或者遇到return ,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或返回,该方法也就执行完毕。

3. 自下而上的递归(递推,数学归纳,动态规划)

  1. 解决简单情况下的问题推广到复杂的情况(由小到大,逐步递推)
  2. 若递推次数很明确,用迭代
  3. 若有封闭形式,(递推公式),可以直接求解。
  4. 递归和迭代地运算顺序是一样的,

1. 上楼梯

有个小孩上楼梯,楼梯有n阶台阶,小孩一次可以上1阶,2阶,3阶。
请实现一个方法,计算小孩有多少种上楼的方式
为了防止溢出,请将结果Mod 1000000007
给定一个正整数n,返回一个数,代表上楼的方式

package 递归;
/*分析:
 * 自下而上的递推:就是先从较为简单的开始,之后再复杂。
 * 梯数   走法
 * 1     1
 * 2     2
 * 3     4
 * 4     7
 * 5     13
 * 从梯数是4时,可以使用前三个的结果
 * 可以:
 * 3+1  1种走法
 * 2+2  剩两梯时,由前知,2种走法
 * 1+3  剩三梯时,由前知,4种走法
 * 总结:
 * 梯数为n,f(n)=f(n-1)+f(n-2)+f(n-3)
 * 得到递推公式*/
public class Digui {
	static final long mod = 1000000007;
	public static void main(String[] args) {
		 System.out.println(recursion1(4));
		 System.out.println(recursion2(4));

	}
	//由递归法做(简洁)
	public static long recursion1(int n) {
		if(n<0) return 0;
		if(n==0||n==1) return 1;
		if(n==2) return 2;
		return recursion1(n-1)%mod + recursion1(n-2)%mod +recursion1(n-3)%mod;
	}
	//由迭代法做(理解推理的过程)
	public static long recursion2(int n) {
		if(n<0) return 0;
		if(n==0||n==1) return 1;
		if(n==2) return 2;
		if(n==3) return 4;
		long x1 = 1;
		long x2 = 2;
		long x3 = 4;
		for(int i=4;i<=n;i++) {//不断地更新三个变量
			long x = x1;
			x1 = x2 % mod;
			x2 = x3 % mod;
			x3 = ((x1 + x2)%mod +x)%mod;
		}
		return x3;
	}
}

2. 机器人走方格

有一个X乘以Y的网络,一个机器人只能走格点且向右或向下走,从左上角走到右下角,请设计一个算法,计算机器人有多少种走法
给定两个正整数,int x, int y ,返回机器人的走法数目,保证x+y 小于等于12

package 递归;
/*分析:
 X=Y=1  不动,也算1种走法
 X=1 Y=2  1种
 X=2 Y=1  1种
 X=2 Y=2  2种
 X=3 Y=2  3种
 X=2 Y=3  3种
 X=3 Y=3  6种
 分析:
 当是X=3 Y=2时,若向下走,就面临一个 X=2 Y=2 由前方知道有2种, 若向右走,就面临一个 X=3 Y=1 有1种, 2+1=3
 当是X=2 Y=3时,若向下走,就面临一个 X=1 Y=3 由前方知道有1种, 若向右走,就面临一个 X=2 Y=2 有2种, 1+2=3
 当是X=3 Y=3时, 若向下走,就面临一个X=2 Y=3 由前方知道有3种  若向右走,就面临一个 X=3 Y=2  有3种  3+3=6
 往右走列数减1,往下走行数减1
 递推公式:  f(x,y) = f(x,y-1) + f(x-1,y)
*/
public class DiGui2 {

	public static void main(String[] args) {
		System.out.println(zou1(3, 3));
		System.out.println(zou2(3, 3));
		 
	}
	//递归
	public static  int  zou1(int x, int y) {
		 if(x==1||y==1) {
			 return 1;
		 }
		 return zou1(x, y-1)+zou1(x-1, y);
	}
	
	//迭代(要考虑需要多少变量来不断地更新数据,技巧看递推公式,此处有两个参数,需要二维,用二维数组来保存所有变量)
	public static  int  zou2(int m, int n) {
		int[][] state = new int[m+1][n+1];
		for(int i=1;i<=n;i++) {//初始化第一行
			state[1][i]=1;
		}
		for(int i=1;i<=m;i++) {//初始化第一列
			state[i][1]=1;
		}
		//从第二行第二列开始填充变化的数值(走法),每个元素等于它的相对应的上一行,左一列的两个数之和
		for(int i=2;i<=m;i++) {
			for(int j=2;j<=n;j++) {
				state[i][j] = state[i][j-1]+state[i-1][j];
			}
		}
		return state[m][n];	
	}
}

3. 硬币表示某个具体的给定值(较难)

package 递归;
/*已知有1分,5 分,10分, 25分 要凑成n分,共有多少种组合?
 *分析:
 *n=1,2,3,4    有1种
 *n=5,6,7,8,9   有2种
 *n=10,11,12,13,14         有4种
 *n=15         有6种
 *当n=10 可以:
 *1x10  1种
 *0x10  0x5 1种  1x5  1种   2x5 1种  
 *
 *当n=15 可以:
 *0x10  0x5 1种  1x5  1种   2x5 1种  3x5 1种
 *1x10  0x5 1种  1x5  1种
 *
 *发现:
 *f(15) = f(0个10,剩下15,用1和5凑)+f(1个10,剩下55,用1和5凑)
 *f(25) = f(0个25,剩下25,用1,5,10凑)+f(1个25,剩下0,用1,5,10凑)
 *f(50) = f(0个25,剩下50,用1,5,10凑)+f(1个25,剩下25,用1,5,10凑)
 *当n=100,就可以有 0x25  1x25  2x25  3x25  4x25 
 * 写递推公式:
 * 1. 参数:调用者只决定最大面值用多少个,剩下的面值,用其他剩余的硬币类去凑, 需要两个参数,要凑的数目 和 凑时可以使用的硬币面值种类
 */

 

public class YingBi {

	int[] coins = {1,5,10,25};
	public static void main(String[] args) {
		 for(int i=1;i<=100;i++) {
			 System.out.println("总数为"+i+"的组合有"+countWays(i));
		 }
	} 
	/**
	 * @param n 要凑的总面额
	 * @param coins  保存可以选择的面值种类
	 * cur  是可以选择的面值种类个数,对应数组的下标,从0开始
	 *coins[cur] 表示当前可以选的最大面值
	 *关键:在for循环中有递归
	 */
	public static int count(int n,int[] coins,int cur) {
		if(cur == 0) return 1;//出口
		int c =0;//组合数
		for(int i=0;i*coins[cur]<=n;i++) {//执行此处的循环,就是可以实现选择最大面值的个数
			int shengyu = n-i*coins[cur];
			c+=count(shengyu, coins,cur-1);//可供选择最大面值减小,同时可供选择种类
		}
		return c;
	}
	
	public static int countWays(int n) {
		if(n<=0) return 0;
		return count(n, new int[] {1,5,10,25}, 3);
	}
}

4. 应用:

1. 迷宫

如下是8行7列的一个迷宫地图,现在要从最左上角数字0走到最右下角数字0这个位置,实现自动查找路线。

1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1

其中1,表示的是墙,是不能触碰的,0表示的是可以走的路线,通过算法,画出从位置(1,1)到(6,5)路线。

package 递归;

public class MiGong {

	public static void main(String[] args) {
//		定义一个二维数组,将地图先画出来,墙的位置用1表示,可走的路用0表示
		int[][] map = new int[8][7];
		// 地图上下置为1,模拟墙.行不变,列在变
		for (int i = 0; i < 7; i++) {
			map[0][i] = 1;
			map[7][i] = 1;
		}
		// 地图左右置为1,模拟墙.列不变,行在变
		for (int i = 0; i < 8; i++) {
			map[i][0] = 1;
			map[i][6] = 1;
		}
		// 设置挡板
		map[3][1] = 1;
		map[3][2] = 1;
		map[1][2] = 1;
		map[2][2] = 1;
		// 输出地图
		System.out.println("地图原貌:");
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 7; j++) {
				System.out.print(map[i][j] + " ");// 此处有一点,就是print是不换行输出,而println是换行输出
			}
			System.out.println();
		}
		System.out.println("使用递归回溯给小球找路:");
		setWay(map, 1, 1);
		System.out.println("地图此时面貌:");
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 7; j++) {
				System.out.print(map[i][j] + " "); 
			}
			System.out.println();
		}
	}

	// 使用递归回溯来给小球找路
//从起始位置(1,1)到终点位置(6,5),则说明通路找到。
//约定:当map[i][j]=0,表示该点没有走过,为1表示墙,为2表示通路,可以走;为3表示该位置已经走过,但是走不通
//再定一个走的策略:先往下,右,上,左方向的顺序;不同的策略,就会有不同的路径。
//如果该点走不通,再回溯;而且因为递归都是面向同一个地图数组,即在栈中的是引用变量。
	/**
	 * @param map 表示地图
	 * @param i   起始位置
	 * @param j   终点位置
	 * @return 找到就返回true
	 */
	public static boolean setWay(int[][] map, int i, int j) {
		if (map[6][5] == 2) {// 通路已经找到
			return true;
		} else {
			if (map[i][j] == 0) {// 如果当前这个点还没有走过,
				// 按照策略:下,右,上,左方向走
				map[i][j] = 2;// 假定该点可以走通的,
				if (setWay(map, i + 1, j)) {// 向下走
					return true;
				} else if (setWay(map, i, j + 1)) {// 向右走
					return true;
				} else if (setWay(map, i - 1, j)) {// 向上走
					return true;
				} else if (setWay(map, i, j - 1)) {// 向左走
					return true;
				} else {// 执行到此处,说明该点面向四个方向都走不通,那就标记一下
					map[i][j] = 3;
					return false;
				}
			} 
			else {// map[i][j]!=0,那么它有可能是1(墙),2(已经走过,不要反复走),3(已经走过,但是死路)
				return false;
			}
		}
/*执行结果:
 地图原貌:
1 1 1 1 1 1 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 1 1 1 1 
使用递归回溯给小球找路:
地图此时面貌:
1 1 1 1 1 1 1 
1 2 0 0 0 0 1 
1 2 2 2 0 0 1 
1 1 1 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 2 2 1 
1 1 1 1 1 1 1 */
	}
}
 	
/*分析:
 * 从(1,1)开始,执行map[1][1] = 2,先调用递归 向下走,setWay(map, 2, 1)
 * 进入新的方法,setWay(map, 2, 1),执行map[2][1] = 2,再调用递归 向下走,走不通,然后向右走,可以
 * 就是此执行过程的每个探测点,先往下,走不通,则向右,走通就不用修改map[i][j] = 2,一直到终点,没有回溯;
 * 那回溯怎么体现呢?
 * 可以再增添挡板
    map[1][2] = 1;
    map[2][2] = 1
    结果:
    地图原貌:
    1 1 1 1 1 1 1 
    1 0 1 0 0 0 1 
    1 0 1 0 0 0 1 
    1 1 1 0 0 0 1 
    1 0 0 0 0 0 1 
    1 0 0 0 0 0 1 
    1 0 0 0 0 0 1 
    1 1 1 1 1 1 1 
    使用递归回溯给小球找路:
    地图此时面貌:
    1 1 1 1 1 1 1 
    1 3 1 0 0 0 1 
    1 3 1 0 0 0 1 
    1 1 1 0 0 0 1 
    1 0 0 0 0 0 1 
    1 0 0 0 0 0 1 
    1 0 0 0 0 0 1 
    1 1 1 1 1 1 1 

*分析:
*先是map[1][1] = 2,往下走,走通,再标记map[2][1] = 2,而此时往下,右,上,左都没有走通,那就map[2][1] = 3;
*就会回溯到(1,1),又向右,上,左;都没走通,map[1][1] = 3*/

分析:
1,定义一个二维数组,将地图先画出来,墙的位置用1表示,可走的路用0表示。

2,自定义一个查找路线 下——>右——>上——>左

3,采用递归算法, 实际上是按照策略中的,经过的每个点都递归实现四个方向的判断,走通就标记为2,走不通,就标记为3,并且回溯。
回溯就是利用递归到达边界条件好返回上一层循环

2. 八皇后问题

在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
思路分析:

  1. 第一个皇后先放在第一行第一列
  2. 第一个皇后先放在第二行第一列,判断是否可以,若不符合,继续放在第二列,第三列,依次把所有列都放完,找到一个合适的
  3. 继续第三个皇后,还是第一列,第二列,第三列,直到第八个皇后也能放在一个不冲突的位置,就找到了一个正确解
  4. 当找到了一个正确解,在栈退回到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解全部得到。
  5. 然后再将第一个皇后放在第二列,继续循环执行1,2,3,4的步骤。
    说明:
    棋盘用一维数组表示,arr[i]={0,4,7,5,2,6,1,3} 对应arr下标,,就表示第几行,即第几个皇后,arr[i]=a ,第i+1个皇后,放在第i+1行,第a+1列
package 递归;

public class BaHuangHou {
	//定义一个max表示共有多少个皇后
	int max = 8;
	//定义一个数组,保存皇后放置位置的结果,
	int[] array = new int[max];
	static int count = 0;//统计摆放方式的个数
	public static void main(String[] args) {
		BaHuangHou queue8 = new BaHuangHou();
		queue8.check(0);//先放第一个皇后,到第一行第一列,之后就递归实现各个皇后的摆放
		System.out.printf("一共有%d种解法",count);

	}
	//1. 实现放置第n个皇后(n从0开始)
		private void check(int n) {
			if(n == max) {//说明在放第九个皇后,因为n从0开始,此时n=8,八个皇后已经放好了
				print();
				return;
			}
			else {//依次放入皇后,并判断是否冲突
				for(int i=0;i<max;i++) {
					//先把这个皇后放在该行的第一列
					array[n] = i;
					//判断当放置第n个皇后到i列时,是否冲突
					if(judge(n)) {
						//不冲突,接着放第n+1个皇后,开始递归
						check(n+1);
					}
					//如果冲突,就继续执行for循环的i++,再array[n] = i;将第n个皇后放在本行的后移的位置
				}
	//递归放在for循环,check是每一次递归都有这个for循环,里面有回溯,就是放到某一个时,发现原先选择不优或不符合要求,就会退回上一步重新选择,这种走不通就退回再走,就是回溯
				//在找到一个正确解后,就会挥到上一个栈,而这个栈里面的for会继续寻找,运行完for循环可能找到了其他摆放方式
			}
		}

 	//2. 查看放置第n个皇后时(n从0开始),检测该皇后是否和已经摆放的皇后冲突
 	private boolean judge(int n) {
 		for(int i=0;i<n;i++) {
//array[i]==array[n]  判断第n个皇后与已经摆放的皇后在同一列
//Math.abs(n-i)==Math.abs(array[n]-array[i]) 判断第n个皇后与第i个皇后在同一斜线  行间距等于列间距,在同一斜线
 			if(array[i]==array[n] || Math.abs(n-i)==Math.abs(array[n]-array[i])) {
 				return false;
 			}
 		}
 		return true;
 	}
 	
	//3.实现将皇后放置位置输出,遍历数组
		private void print() {
			count++;
			for(int i=0;i<array.length;i++) {
				System.out.print(array[i]+" ");
			}
			System.out.println();
		}
//输出:
0 4 7 5 2 6 1 3 
0 5 7 2 6 3 1 4 
0 6 3 5 7 1 4 2 
0 6 4 7 1 3 5 2 
1 3 5 7 2 0 6 4 
1 4 6 0 2 7 5 3 
1 4 6 3 0 7 5 2 
1 5 0 6 3 7 2 4 
1 5 7 2 0 3 6 4 
1 6 2 5 7 4 0 3 
1 6 4 7 0 3 5 2 
1 7 5 0 2 4 6 3 
2 0 6 4 7 1 3 5 
2 4 1 7 0 6 3 5 
2 4 1 7 5 3 6 0 
2 4 6 0 3 1 7 5 
2 4 7 3 0 6 1 5 
2 5 1 4 7 0 6 3 
2 5 1 6 0 3 7 4 
2 5 1 6 4 0 7 3 
2 5 3 0 7 4 6 1 
2 5 3 1 7 4 6 0 
2 5 7 0 3 6 4 1 
2 5 7 0 4 6 1 3 
2 5 7 1 3 0 6 4 
2 6 1 7 4 0 3 5 
2 6 1 7 5 3 0 4 
2 7 3 6 0 5 1 4 
3 0 4 7 1 6 2 5 
3 0 4 7 5 2 6 1 
3 1 4 7 5 0 2 6 
3 1 6 2 5 7 0 4 
3 1 6 2 5 7 4 0 
3 1 6 4 0 7 5 2 
3 1 7 4 6 0 2 5 
3 1 7 5 0 2 4 6 
3 5 0 4 1 7 2 6 
3 5 7 1 6 0 2 4 
3 5 7 2 0 6 4 1 
3 6 0 7 4 1 5 2 
3 6 2 7 1 4 0 5 
3 6 4 1 5 0 2 7 
3 6 4 2 0 5 7 1 
3 7 0 2 5 1 6 4 
3 7 0 4 6 1 5 2 
3 7 4 2 0 6 1 5 
4 0 3 5 7 1 6 2 
4 0 7 3 1 6 2 5 
4 0 7 5 2 6 1 3 
4 1 3 5 7 2 0 6 
4 1 3 6 2 7 5 0 
4 1 5 0 6 3 7 2 
4 1 7 0 3 6 2 5 
4 2 0 5 7 1 3 6 
4 2 0 6 1 7 5 3 
4 2 7 3 6 0 5 1 
4 6 0 2 7 5 3 1 
4 6 0 3 1 7 5 2 
4 6 1 3 7 0 2 5 
4 6 1 5 2 0 3 7 
4 6 1 5 2 0 7 3 
4 6 3 0 2 7 5 1 
4 7 3 0 2 5 1 6 
4 7 3 0 6 1 5 2 
5 0 4 1 7 2 6 3 
5 1 6 0 2 4 7 3 
5 1 6 0 3 7 4 2 
5 2 0 6 4 7 1 3 
5 2 0 7 3 1 6 4 
5 2 0 7 4 1 3 6 
5 2 4 6 0 3 1 7 
5 2 4 7 0 3 1 6 
5 2 6 1 3 7 0 4 
5 2 6 1 7 4 0 3 
5 2 6 3 0 7 1 4 
5 3 0 4 7 1 6 2 
5 3 1 7 4 6 0 2 
5 3 6 0 2 4 1 7 
5 3 6 0 7 1 4 2 
5 7 1 3 0 6 4 2 
6 0 2 7 5 3 1 4 
6 1 3 0 7 4 2 5 
6 1 5 2 0 3 7 4 
6 2 0 5 7 4 1 3 
6 2 7 1 4 0 5 3 
6 3 1 4 7 0 2 5 
6 3 1 7 5 0 2 4 
6 4 2 0 5 7 1 3 
7 1 3 0 6 4 2 5 
7 1 4 2 0 6 3 5 
7 2 0 5 1 4 6 3 
7 3 0 2 5 1 6 4 
一共有92种解法		
}

posted on 2022-03-11 17:31  精致猪猪侠  阅读(82)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3