一、什么是递归
简单的说,程序调用自身的编程技巧称为递归( recursion)
递归会遵守如下准则:
- 每执行一次方法,就会创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响
- 如果方法的参数是引用类型的,如数组,那么会共享这个变量
- 递归必须有退出条件,而且需要向退出递归的条件逼近,否则就会无限递归,出现StackOverflowError
- 当一次方法执行完毕或者是return之后,就会返回到调用它的地方
其中第4个算是递归的一个必要条件,而其他的,算是递归的特征
例如:求n的阶乘
public static int factorial(int n) {
if (n == 1) {
return 1;
}
return n * factorial(n - 1);
}
可以看到,这里的退出条件是n==1,而会不停的通过调用 factorial(n - 1)来逼近1,而如果是 factorial(n + 1),那么这个递归就不会结束了
二、递归的调用过程
这里还是以n的阶乘来讲解递归过程,如计算4的阶乘,我们可以在main函数里面调用函数计算:
public static void main(String[] args) {
int result=factorial(4);
System.out.println(result);
}
计算的调用过程如下:

三、小球回溯
(一)问题描述
在如图所示的一个n行m列迷宫里面,某一个地方有一个小球,还有一些障碍物,请找出一条达到出口的路线(出口在右下角)

我对原始问题做了以下变动:
- 行数和列数可以变化
- 小球的位置随机生成
- 障碍物随机生成(四周的除外)
- 如果没有一条路径,就打印没有路径
(二)思路分析
迷宫图的生成
为了能够很好的表示迷宫图,这里定义一个对象来表示迷宫图:
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Map {
private int startX;
private int startY;
private int[][] map;
}
其中startX表示小球起点所在行;startY表示小球起点所在列(都是从下标0开始);map表示整个地图,有障碍物的地方为1,没有障碍物的地方是0,小球起点位置为9,例如其中一个地图为:
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 1
1 1 1 1 1 9 0 1
1 1 0 0 1 1 0 1
1 0 0 1 0 0 1 1
1 0 0 0 0 0 0 1
1 0 0 0 0 1 0 1
1 1 1 1 1 1 1 1
此时startX为2,startY为5
- 小球位置的生成
使用随机数即可 - 障碍物的生成
四周的设置成1,然后再随机一定数量的位置出来,设置成1 - 相应的方法名为:
generateMap
查找路线
- 对于小球移动到的每一个位置,对下一步按照下、右、上、左这种策略来前进,也就是说先尝试向下走,如果走不通就尝试向右边走等等。如果某个方向走通了,那么这个位置需要标志成走过了(用2记录),如果四个方向都不可以走通,证明这个点是不能走的,需要进行标记,并回到上一步;
- 从起点位置开始走;所以初始的时候,把map[startX][startY] 标志成0,等找到路线,再重新设置成9,方便知道起点是哪里;
- 假如小球当前所在的位置为map[x][y],向下走其实就是x+1,向右走是y+1,向上是x-1,向左边走是y-1
(三)代码实现
package com.firewolf.javabase.s005_recursion.realcase;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 迷宫问题
* 问题描述:
* 在一个迷宫里面,某一个地方有一个小球,还有一些障碍物,请找出一条达到出口的路线(出口在右下角)
*/
public class Maze {
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Map {
private int startX;
private int startY;
private int[][] map;
}
public static void findWay(int[][] map, int x, int y) {
map[x][y] = 0; //走之前,把起点位置标成0,否则第一步走不出去
boolean b = goOneStep(map, x, y);
if (!b) {
System.out.println("没有可以走的路....");
}
map[x][y] = 9; //走完之后,恢复起点位置标志,方便查看
}
/**
* 走一步
* @param map
*/
public static boolean goOneStep(int[][] map, int x, int y) {
int mapRows = map.length; //map的行数,比小球能走的多两行(墙)
int mapCols = map[0].length; //map的列数,比小球能走的多两行(墙)
if (map[mapRows - 2][mapCols - 2] == 2) { //表明找到了
return true;
}
if (map[x][y] == 0) {//当前这个位置没有走过
//走这个点
map[x][y] = 2;
//接下来往其他方向走,按策略 下、右、上、左 走,如果走不通,都走不通就会回到上一步;
if (goOneStep(map, x + 1, y)) {
return true;
} else if (goOneStep(map, x, y + 1)) {
return true;
} else if (goOneStep(map, x - 1, y)) {
return true;
} else if (goOneStep(map, x, y - 1)) {
return true;
} else {
//上下左右都行不通,证明这个点走不了
map[x][y] = 3;
return false;
}
} else { //已经走过了
return false;
}
}
/**
* 产生地图
* @param rows 行数
* @param colums 列数
*/
public static Map generateMap(int rows, int colums) {
//教研输入是否正确
assert rows > 0 && colums > 0;
//初始化数组
int[][] mapArry = new int[rows + 2][colums + 2];
//把四周围上墙,就是把赋值为1,默认是0 的表示可以走的路
for (int i = 0; i < rows + 2; i++) {
mapArry[i][0] = 1;
mapArry[i][rows + 1] = 1;
}
for (int i = 0; i < colums + 2; i++) {
mapArry[0][i] = 1;
mapArry[colums + 1][i] = 1;
}
Map map = new Map();
//随机生成起点位置,用2标志
Random random = new Random();
int x = 1 + random.nextInt(rows);//行标
int y = 1 + random.nextInt(colums); //列标
mapArry[x][y] = 9;
map.startX = x;
map.startY = y;
//随机生成障碍物(可以走的1/3个吧)
int count = rows * colums / 3;
for (int i = 0; i < count; i++) {
x = 1 + random.nextInt(rows);//行标
y = 1 + random.nextInt(colums); //列标
if (mapArry[x][y] != 9 && !(x == rows && y == colums)) {
mapArry[x][y] = 1;
}
}
map.map = mapArry;
return map;
}
public static void printMap(int[][] map) {
System.out.println("-----------------------");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.printf("%d ", map[i][j]);
}
System.out.println();
}
System.out.println();
System.out.println();
System.out.println();
}
}
四、八皇后
(一)问题描述
皇后能横向,纵向和斜向移动,在这三条线上的其他棋子都可以被吃掉。所谓八皇后问题就是:将八位皇后放在如下图所示的一张8x8的棋盘上,使得每位皇后都无法吃掉别的皇后,(即任意两个皇后都不在同一条横线,竖线和斜线上),问一共有多少种摆法。

此问题是在1848年由棋手马克思·贝瑟尔提出的,后面陆续有包括高斯等大数学家们给出自己的思考和解法,所以此问题不只是有年头了,简直比82年的拉菲还有年头,我们今天不妨尝尝这老酒;
其实这个问题可以扩展成为在一个n*n的棋盘上进行求解;
(二)思路分析
- 通过一个数组positions = new int[n] 来存放一种可能的结果
positions[n-1]=i 表示将第n个皇后摆放在第n行的第i+1列
行从下往上数,列从左往右数 - 我们从第一个皇后开始摆放,没问题的话就开始摆放第二个皇后,如果摆放到了第n+1个,那么证明所有的n个皇后都摆放完了,那么就意味着有一种解法了;
- 对于第n个皇后,我们可以放在第n-1行的0到n列,放完之后,我们需要进行判断这个位置可不可以的,如果不与前面的冲突,就可以继续放置第n+1个皇后;
- 判断是否冲突:其实也就是判断第n个与前面的n-1个是否冲突;
①. 行:由于第n个会放在第n-1行,所以这些皇后不可能在同一行,不用判断;
②. 列:由于第第n个元素放置的列为positions[n-1],所以通过比较与前面几个数组值是否一致既可;
③.是否在同一斜线
对于任何两个皇后n和i+1个皇后,满足如下条件:

如果所示,第n个皇后的坐标为:(positions[n-1],n-1),而第i+1个皇后的坐标为:(positions[i],i),要满足在一条斜线上,则红线长度和绿线商都一样,有两种情况:
a. 第n个在第i+1个的右边,则:positions[n-1]-positions[i]=n-1-i;
b. 第n个在左边,则:positions[i]-positions[n-1]=n-1-i
总之,需要满足:Math.abs(n - 1 - i) == Math.abs(positions[n - 1] - positions[i])
(三)代码实现
package com.firewolf.javabase.s005_recursion.realcase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 八皇后
*
*/
public class QueueN {
private static int[] positions = null;
private static int count = 0;
private static List<int[]> result = new ArrayList<>();
public static List<int[]> queueN(int n) {
positions = new int[n];
position(1, n);
return result;
}
/**
* 摆放第n个换后
* @param n
*/
public static void position(int n, int total) {
if (n > total) { // n 已经大于最大皇后数量了,就证明OK了
result.add(Arrays.copyOf(positions, total));
count++;
return;
}
for (int i = 0; i < total; i++) {
positions[n - 1] = i; //让第n个皇后放在第n-1行的各个位置进行测试
if (check(n)) {
position(n + 1, total);
}
}
}
//检查第n个有没有问题
private static boolean check(int n) {
for (int i = 0; i < n - 1; i++) {
//判断在同一列
if (positions[i] == positions[n - 1]
//判断是不是在一条斜线,在同一条斜线的有两类:
//1. 右上到左下,45度角,
//2. 左上到右下,135,但是不管怎么样,两条边的长度一样,水平方向边长:Math.abs(positions[n - 1] - positions[i])
// 垂直方向边长Math.abs(n - 1 - i)
|| Math.abs(n - 1 - i) == Math.abs(positions[n - 1] - positions[i])
) {
return false;
}
}
return true;
}
}
2万+

被折叠的 条评论
为什么被折叠?



