算法:递归
介绍
递归是一种常见的算法,由递归书写的代码较为简洁,一般用于处理有规律的循环问题,与for类似。直白点说,递归就是函数自己调用自己;在使用递归时,注意留出口(递归循环结束),否则程序会无限递归下去,最终ERROR。当然因为递归表示比较简洁,理解起来也是算法的一大难点。
例子
下面写2个关于递归的例子:
- 打印3!(3的阶乘)
- 从0到9报数
//计算阶乘
package DataStructure;
public class FactorialWithRecursion {
public static void main(String[] args) {
System.out.println(factorial(3));
}
public static int factorial(int n) {
if (n == 1) {//出口
return n;
}
return n*factorial(n-1);
}
}
递归的本质还是栈,当要运行一道程序时,java虚拟机会在栈中开辟一道空间
下面用一张图来说明这几行代码
假如我们没有给出对应的出口,n==1,编译器会报错
从0到9报数
package DataStructure;
public class RecursionDemo2 {
public static void main(String[] args) {
System.out.println(print(9));
}
public static int print(int num) {
if(num==0) {
return 0;
}
System.out.println(print(num-1));
return num;
}
}
排列组合
排列:上,下,左,右
很明显,上下左右的排列可以用递归来完成
- 需要2个容器,一个容器temp来存储暂时读取的字符,还有一个list用来存储最终结果
- 每次把字符存入temp时,要考虑temp中是否包含此字符,(用for遍历)如果有,跳过添加
- 当暂时存储的容器temp为4时,把此时temp中的字符串存入list,然后返回
- 返回后要将temp最后一个元素清除,例如:此时temp是“上下左右”,添加进list后,去除temp最后一个元素,temp就是“上下左”,由于此时for已循环,回到上一个递归:temp为“上下右”
暂时容器使用stringbuffer
最终容器使用arraylist
package DataStructure;
import java.util.ArrayList;
public class DirectionRecursion {
public static void main(String[] args) {
char[] directions = {'上','下','左','右'};
ArrayList<String> list = new ArrayList<>();
test(list, directions, new StringBuffer());
Iterator<String> itr = list.iterator();
int count = 0;
while(itr.hasNext()) {
System.out.println(itr.next());
count+=1;
}
System.out.println("一共有"+count+"种可能");
}
public static void test(ArrayList<String> list,char[] directions,StringBuffer temp) {
if(temp.length() == directions.length) {
list.add(temp.toString());
return;
}
for(int i=0;i<directions.length;i++) {
if(temp.toString().indexOf(directions[i])!=-1) continue;
temp.append(directions[i]);
test(list, directions, temp);
temp.deleteCharAt(temp.length()-1);
}
}
}
利用递归解决迷宫问题
思路
问题:小球在迷宫中运动,怎样到达终点
- 使用二维数组先创建迷宫
- 创建一个boolean方法,接收map和初始位置i, j
- 数字1表示墙,数字2表示可以走的点,数字3表示不能走的点
当小球寻路时(下->右->左->上),我们需要用递归的方法:
先判断小球是否已经到达终点,到达终点,返回true
没有就进入寻路的递归中:我们假设下一步能走的通,即map[i] [j] =2; 先向下走得通,返回true,直到走不通,将走不通的点标为3,返回false(返回上一个位置);向下继续走不通,就向右走,循环递归,直到走不通为止;
代码
package DataStructure;
public class MazeRecursionDemo {
public static void main(String[] args) {
// 构建地图
int[][] map = new int[10][10];
for (int a = 0; a < map[0].length; a++) {
map[0][a] = 1;
map[map.length - 1][a] = 1;
}
for (int b = 0; b < map.length; b++) {
map[b][0] = 1;
map[b][map[0].length - 1] = 1;
}
map[1][5] = 1;
map[2][5] = 1;
map[3][5] = 1;
map[3][1] = 1;
map[3][2] = 1;
map[3][3] = 1;
map[5][3] = 1;
map[5][4] = 1;
map[5][5] = 1;
map[5][6] = 1;
map[5][7] = 1;
map[5][8] = 1;
System.out.println("\t地图");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
tryWay(map, 1,1);
System.out.println();
System.out.println("\t路线");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
public static boolean tryWay(int[][] map, int i, int j) {
//如果到达终点,直接返回true,程序结束
if (map[8][8] == 2) {
return true;
}
//没有到达终点
else {
//如果所在位置之前没有走过,之前走过标为2,也不能走
if(map[i][j] ==0) {
//假设能走通
map[i][j] = 2;
//向下走得通
if (tryWay(map, i+1, j)) {
return true;
}
//向右走得通
else if (tryWay(map, i, j+1)) {
return true;
}
//向左走得通
else if (tryWay(map, i, j - 1)) {
return true;
}
//向上走得通
else if (tryWay(map, i-1, j)) {
return true;
}
//上下左右都走不通
else {
//表示这个点不能走,返回上一个节点
map[i][j] = 3;
return false;
}
}
//所在位置是墙,或不能走的位置,或已经走过的地方
else {
//map[i][j] =1或2或3
return false; //返回false,返回上一个节点位置
}
}
}
}
迷宫最优解(目前)
小球在迷宫中移动,其走的路径长短很大一部分与其设定的方向有关,当我们开始递归时,小球的方向就已经确定下来,所以为了找出小球按照哪种方向形式走才能达到最小路径,我们需要使用排列组合;通过“上下左右”的排列组合,得出所有方向,然后在程序中我们要小球按照我们给出的方向行走,最终统计步数,相比较得出最佳方向
"上下左右"的排列组合上文已经给出解法,下面我们要与前面的迷宫程序相结合
因为小球在迷宫中走的方向都是我们设定好的,所以在递归时我们要加上一个String direction
for(int a=0;a<4;a++) {
//由于一定要判断上下左右的方向,但是顺序不同,我用一个for循环来读取direction中的四个方向
switch (direction.charAt(a)) {
case '上':
//向上走得通
if (tryWay(map, i-1, j,direction)) {
return true;
}
break;
case '下':
//向下走得通
if (tryWay(map, i+1, j, direction)) {
return true;
}
break;
case '左':
//向左走得通
if (tryWay(map, i, j - 1, direction)) {
return true;
}
break;
case '右':
//向右走得通
if (tryWay(map, i, j+1, direction)) {
return true;
}
break;
default:
break;
}
}
最终
package DataStructure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class MazeRecursionDemo2 {
public static void main(String[] args) {
// 构建地图
int[][] map = new int[10][10];
InitMap(map); //地图初始化
System.out.println("\t地图");
PrintMap(map);
//求出最短路径
char[] directions = {'上','下','左','右'};
ArrayList<String> list = new ArrayList<>();
Search(list, directions, new StringBuffer());
//Map是用来储存此时的方向与小球运动步数的
HashMap<String, Integer> dic = new HashMap<>();
Iterator<String> itr = list.iterator();
while(itr.hasNext()) {
String move = itr.next();
//寻路
tryWay(map, 1,1,move);
//统计方向与步数
dic.put(move, Count(map));
//完了要将map还原(初始化)
InitMap(map);
}
System.out.println();
System.out.println("所有方向可能性与步数");
//集合的打印
for(Entry<String, Integer> i : dic.entrySet()) {
System.out.println("\t"+i.getKey()+"----"+i.getValue());
}
//在集合中比较大小
Collection<Integer> values = dic.values();
int smallest = Collections.min(values);
System.out.println();
System.out.println("最优的路径----步数:"+smallest);
for(Entry<String, Integer> i : dic.entrySet()) {
if(i.getValue() == smallest) {
System.out.println("\t"+i.getKey());
tryWay(map, 1,1,i.getKey());
PrintMap(map);
InitMap(map);
}
}
}
//打印地图
public static void PrintMap(int[][] map) {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
//初始化地图
public static void InitMap(int[][] map) {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
map[i][j] =0;
}
}
for (int a = 0; a < map[0].length; a++) {
map[0][a] = 1;
map[map.length - 1][a] = 1;
}
for (int b = 0; b < map.length; b++) {
map[b][0] = 1;
map[b][map[0].length - 1] = 1;
}
map[1][5] = 1;
map[2][5] = 1;
map[3][5] = 1;
map[3][1] = 1;
map[3][2] = 1;
map[3][3] = 1;
map[5][3] = 1;
map[5][4] = 1;
map[5][5] = 1;
map[5][6] = 1;
map[5][7] = 1;
map[5][8] = 1;
}
//统计小球走的路径距离
public static int Count(int[][] map) {
int cnt=0;
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
if(map[i][j] ==2 ||map[i][j] ==3) {
cnt++;
}
}
}
return cnt;
}
//使用递归改变方向(上下左右的排列组合)
public static void Search(ArrayList<String> list,char[] directions,StringBuffer sb) {
if(sb.length() == directions.length) {
list.add(sb.toString());
return;
}
for(int i=0;i<directions.length;i++) {
if(sb.toString().indexOf(directions[i])!=-1) continue;
sb.append(directions[i]);
Search(list, directions, sb);
sb.deleteCharAt(sb.length()-1);
}
}
//使用递归寻找出口
public static boolean tryWay(int[][] map, int i, int j, String direction) {
//如果到达终点,直接返回true,程序结束
if (map[8][8] == 2) {
return true;
}
//没有到达终点
else {
//如果所在位置之前没有走过,之前走过标为2,也不能走
if(map[i][j] ==0) {
//假设能走通
map[i][j] = 2;
for(int a=0;a<4;a++) {
switch (direction.charAt(a)) {
case '上':
//向上走得通
if (tryWay(map, i-1, j,direction)) {
return true;
}
break;
case '下':
//向下走得通
if (tryWay(map, i+1, j, direction)) {
return true;
}
break;
case '左':
//向左走得通
if (tryWay(map, i, j - 1, direction)) {
return true;
}
break;
case '右':
//向右走得通
if (tryWay(map, i, j+1, direction)) {
return true;
}
break;
default:
break;
}
}
//上下左右都走不通
//表示这个点不能走,返回上一个节点
map[i][j] = 3;
return false;
}
//所在位置是墙,或不能走的位置,或已经走过的地方
else {
//map[i][j] =1或2或3
return false; //返回false,返回上一个节点位置
}
}
}
}
八皇后问题
题目:需要在8*8的棋盘上摆棋子,每行摆一个,每个棋子不能在同一列或同一对角线上
需要用到回溯思想,比如第4行的棋子不能放在和第3行棋子的同一列上,为了避免这样,第4行的棋子在第四行上一个一个试,假如都不可以,回到第3行,更改第3行的棋子位置,再尝试第4行,戒指第5行,不行就往回
程序
package DataStructure;
public class EightGueenDemo {
//定义一个max表示一共有多少个皇后
int max =8;
//定义一个一维数组,数组下标表示第几行,下标对应的数表示第几列
int[] array =new int[max];
int count=0;
public static void main(String[] args) {
EightGueenDemo queen = new EightGueenDemo();
queen.check(0);
System.out.printf("一共有%d种可能",queen.count);
}
//打印数组
public void print() {
for(int i=0;i<array.length;i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
//判断是否安全(冲突)
//n表示第n个皇后
public boolean safe(int n) {
for(int i=0;i<n;i++) {
//表示在同一列或同一斜线
if(array[i] ==array[n] || Math.abs(n-i)==Math.abs(array[n] - array[i])) {
return false;
}
}
return true;
}
//编写方法,放置第n个皇后
public void check(int n) {
if(n==max) {
print();
count++;
return;
}
//一共要放8个皇后,8个循环递归,
for(int i=0;i<max;i++) {
//先把该皇后放在第n行第i列
array[n] =i;
//是否冲突
if(safe(n)) {
//如果不冲突,接着放第n+1个皇后,递归
check(n+1);
}
//如果冲突,i++,将皇后在第n行向后移动一位,直到不冲突
}
}
}