递归算法
概念
程序调用自身的编程技巧称为递归( recursion)。递归作为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
了解递归算法的调用机制
递归所要遵循的重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量时独立的,不会互相影响
- 如果方法中使用的是引用类型变量,就会共享该引用类型的数据(这个以后会慢慢讲解)
- 递归必须要向退出的递归条件逼近,否则就是无限递归了,因为栈是有容量的,无限递归会引发栈溢出的错误
- 当一个方法执行完毕,或者遇到return,就会返回,而且遵守谁调用,就讲结果返回给谁,同时当方法执行完毕或返回时,该方法也就执行完毕了,可以通过上图来理解这段话。
迷宫回溯问题
迷宫问题概述
迷宫回溯问题如下图所示,红色的代表墙体,黑色的代表小球,五角星代表终点,小球需要根据制定的规则走到终点
思路分析
首先明确一点,我们用二维数组模拟小球的运动轨迹即可,1代表墙体,0代表该地点还没有被走过,2代表该地点已经被走过,3代表该地点已经被走过但是走不通,对于3的理解,我们可以认为在走迷宫时没到一个分叉路口标记我们走的路,下次再遇到就不走了。
除此之外,我们还需要制定小球行进的规则,下-右-上-左,也就是说每到一个地点准备往下一个地点走时,都先通过我们制定的规则来优先判断
代码实现
public class Maze {
public static void main(String[] args) {
//构建地图
int[][] map = new int[8][7];
//填充墙壁
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][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] + " ");
}
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();
}
}
/**
* 说明
* map是地图
* 小球从(1,1)开始走,如果走到(6,5)代表路通
* 0代表没有走过,1代表墙,不允许通过,2代表走过的点位,3代表走过的点位,但走不通
* 定制一个小球走的策略,下-右-上-左
* @param map 地图
* @param x 终点的坐标
* @param y 终点的坐标
* @return
*/
public static boolean setWay(int[][] map,int x,int y){
if (map[6][5] == 2){
return true;
}else {
//输出小球形式路径,可以便于更好的理解代码
System.out.printf("x=%d,y=%d\n",x,y);
if (map[x][y] == 0) {
//先假定当前点位为2
map[x][y] = 2;
if (setWay(map, x + 1, y)) {
return true;
} else if (setWay(map, x, y + 1)) {
return true;
} else if (setWay(map, x - 1, y)) {
return true;
} else if (setWay(map, x, y - 1)) {
return true;
} else {
//都走不通,标记点位为3
map[x][y] = 3;
return false;
}
}else {
//map[x][y] == 1,2,3 代表路已经走过并且走不通
return false;
}
}
}
}
八皇后问题
皇后问题概述
在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。
思路分析
- 第一个皇后放在第一行第一列
- 第二个皇后放在第二行第一列,判断是否会出现冲突,如果冲突,则再放到第二列、第三列等,直到找到不冲突的位置
- 继续找第三个皇后不冲突的位置,直到把8个皇后全部安置好,才算是找到一个正确的解法
- 找到正确的解法后,开始回溯,回溯过程,第8个皇后再向下一列移动,看是否可以有正确的解法,如果没有,第7个皇后再向下一列移动,再重新安置第8个皇后。直到第一个皇后放在第一行第一列的所有正确解得到
- 将第一个皇后放在第一行第二列,重复2,3,4步骤,直到第一个皇后放在第一行第八列的正确解法全部得到
代码实现
原本这里需要使用二维数组来实现皇后对应的位置,但是可以通过算法,使用一个一维数组实现,array{1,2,3,4,5,6,7,0}
数组中的下标+1表示第几行,而数组对应的值+1表示第几列
public class Queue8 {
//定义棋盘的大小
int max = 8;
//创建数组
int[] arr = new int[max];
//一共有多少种解法
static int count = 0;
//一共判断了多少次冲突
static int judgeCount = 0;
public static void main(String[] args) {
Queue8 queue8 = new Queue8();
queue8.check(0);
System.out.printf("一共有%d种解法",count);
System.out.printf("一共判断了%d冲突",judgeCount);
}
//放置第n个皇后
private void check(int n){
//如果n为8,代表8个皇后都已经安置完毕,可以直接输出结果
if (n == max){
count++;
print();
return;
}
for (int i = 0; i < max; i++) {
arr[n] = i;
if (judge(n)){
//已经安置,开始递归
check(n+1);
}
//这里不需要else,因为数组的原因,如果冲突的话,会重新执行循环,会修改arr[n]为下一个i
}
}
/**
* 判断当前放置的第n个皇后是否与之前放的皇后冲突
* @param n 第n个皇后
* @return true:不冲突,false:冲突
*/
private boolean judge(int n){
judgeCount++;
for (int i = 0; i < n; i++) {
//1.arr[i] == arr[n] 判断两个皇后是否在同一列
//2.Math.abs(i-n) == Math.abs(arr[i] - arr[n]) 判断两个皇后是否在同一斜线上,
//3.两个皇后是否在同一行上无需判断,因为本身放置的时候数组下标就代表了不同的行
if (arr[i] == arr[n] || Math.abs(i-n) == Math.abs(arr[i] - arr[n])){
return false;
}
}
return true;
}
//输出正确的结果
private void print(){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}