算法:递归

算法:递归

介绍

递归是一种常见的算法,由递归书写的代码较为简洁,一般用于处理有规律的循环问题,与for类似。直白点说,递归就是函数自己调用自己;在使用递归时,注意留出口(递归循环结束),否则程序会无限递归下去,最终ERROR。当然因为递归表示比较简洁,理解起来也是算法的一大难点。

例子

下面写2个关于递归的例子:

  1. 打印3!(3的阶乘)
  2. 从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;
		
	}
}
排列组合

排列:上,下,左,右

在这里插入图片描述

很明显,上下左右的排列可以用递归来完成

  1. 需要2个容器,一个容器temp来存储暂时读取的字符,还有一个list用来存储最终结果
  2. 每次把字符存入temp时,要考虑temp中是否包含此字符,(用for遍历)如果有,跳过添加
  3. 当暂时存储的容器temp为4时,把此时temp中的字符串存入list,然后返回
  4. 返回后要将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);
			
		}
		
	}
}

在这里插入图片描述

利用递归解决迷宫问题

思路

问题:小球在迷宫中运动,怎样到达终点

  1. 使用二维数组先创建迷宫
  2. 创建一个boolean方法,接收map和初始位置i, j
  3. 数字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行向后移动一位,直到不冲突
		}
	}

}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值