递归和回溯算法的使用

1.递归

  • 递归遵循的规则
    在这里插入图片描述

在这里插入图片描述

  • 代码:
    - * @param map :代表传入的地图数组元素8*7
    - * @param i
    - * @param j:表示球的起始坐标(i,j)
    - * @return:这里用2表示走过的路,0表示未走过的路,1表示边界或障碍物,3表示死路
    - * 因为是从左上角开始要走到右下角,我们制定走路的策略为下->右->左->上
				package Game;
				/*
				    * 使用递归来实现迷宫问题,就是一个球从左上角开始走,到达右下角的路线
				    *地图采用二维数组实现,元素为1表示不能走的格子,0表示可以走,2表示走过的 
				 */
				public class MiGong {
				
					public static void main(String[] args) {
						//创建地图8*7,并给好边界
						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 j=0;j<8;j++ ) {
							Map[j][0] = 1;
							Map[j][6] = 1;
								}
						//设置挡板,置为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.printf("%d ",Map[i][j]);	
							}
							System.out.println();
						}
					   
						//测试游戏,传入地图,起始点的行列坐标
						FindWay(Map,1 ,1);
						//走过时的地图显示
						 System.out.println("走过地图显示:");
						for(int i = 0;i<8;i++) {
							for(int j =0;j<7;j++) {
								System.out.printf("%d ",Map[i][j]);	
							}
							System.out.println();
						}
					}
					//编写小球的走路策略
					/**
					 * @param map :代表传入的地图数组元素8*7
					 * @param i
					 * @param j:表示球的起始坐标(i,j)
					 * @return:这里用2表示走过的路,0表示未走过的路,1表示边界或障碍物,3表示死路
					 * 因为是从左上角开始要走到右下角,我们制定走路的策略为下->右->左->上
					 */
					public static boolean FindWay(int[][] map,int i ,int j) {
						//右下角表示最后的目标点,判断到达了目标点
						if(map[6][5]==2) {
							return true;//表示走到最后的点
						}else { //未走到终点继续走
							if(map[i][j]==0) {//表示这个点还未走过
								map[i][j]= 2; //表示这个点可以走通,探索下一个通路点
								if(FindWay(map,i+1 ,j)) {return true; //向右可以走通
								  }else if(FindWay(map,i ,j+1)) {return true; //向下可以走通
									}else if(FindWay(map,i ,j-1)) {return true; //向左可以走通
									   }else if(FindWay(map,i-1 ,j)) {return true; //向上可以走通
								           }else{ map[i][j]=3;  //如果这个点肯定走不通
								                  return false;
								                }
							}else {
								    return false; //说明map的元素非0,就是走过的和走不通的
							}	
						}
						
					}
				}

回溯算法

  • 回溯算法其实就是把所有的结果进行枚举,通过设定条件函数来搜索目标解、做搜索、回溯问题的套路是画图,代码其实 就是根据画出的树形图写出来的

    • 剪枝函数:描述合法解的一般特征,避免搜索不合法解
    • 条件:每一步搜索的解必须符合条件,其所有可能的解可以由空间状态树进行描述
    • 记录:当每次达到结束条件时对结果进行保存,通常是全局变量并作为传递参数
    • 这里以力扣39:组合总和,liweiwei1419 的解答过程进行描述:
  • 要求找数组中元素和满足目标值的元素排列,元素可以重复;它这里采用减的方式,将解空间进行表述。
    示例:输入: candidates = [2, 3, 6, 7],target = 7,所求解集为: [[7], [2, 2, 3]]

    在这里插入图片描述
    这里,只有最后的条件满足为0的数组元素记录返回就行,通过递归调用的方式,当然我这用了累减和累加的两种形式,

class Solution {
    //采用数组元素累加和和满足目标值的解答过程,
    /*List <List<Integer>>  list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List <Integer> ls = new ArrayList<>();
        int[] arr =candidates;
        search(arr,0,ls,0,target); //目标数组,当前索引,子列表集合,当前和,目标和
        return list;
    }
     public void search(int[] arr,int index,List <Integer> ls,int sum,int target){
      if(sum> target){
          return;
      }else if(sum==target){
         List<Integer> e = new ArrayList<Integer>();
         e.addAll(ls); //记录所有满足条件的,
        list.add(e); 
        return;
      }
      for(int i=index;i<arr.length;i++){
         if(arr[i]>target){continue;}  //跳过不符合的值
         ls.add(arr[i]);
         search(arr,i,ls,sum+arr[i],target); //执行完后返回  
         ls.remove(ls.size()-1);//根据下标去掉最后一个元素,最后一个(当前元素不满足时跳过)
      }

     }*/

     //利用减法来做,只有当最后的值为0时作为回溯条件
     List <List<Integer>>  list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List <Integer> ls = new ArrayList<>();   //每次获得子集合的目标元素会进行再次清空处理
        int[] arr =candidates;
        search(arr,0,ls,target,target); //目标数组,当前索引,子列表集合,当前减值,目标和
        return list;
    }
     public void search(int[] arr,int index,List <Integer> ls,int delete,int target){
      if(delete < 0){
          return;
      }else if(delete==0){
         List<Integer> e = new ArrayList<Integer>();
         e.addAll(ls); //记录所有满足条件的,
        list.add(e); 
        return;
      }
      for(int i=index;i<arr.length;i++){
         if(arr[i]>target){continue;}  //跳过不符合的值
         ls.add(arr[i]);
         search(arr,i,ls,delete-arr[i],target); 
         //执行完后返回,索引还是当前的索引,可能满足同一个元素的倍数情形  
         ls.remove(ls.size()-1);//根据下标去掉最后一个元素,当最后一个(当前元素不满足时跳过)
      }

     }
}

八黄后问题
-  **下面针对迷宫问题实现**

  • 思路分析:
    在这里插入图片描述
  • 源代码编写: 若行的差值与列的差值相同,则两位置必定是同一斜线上
				package Game;
				//八皇后问题,利用回溯算法实现,结果用一维数组进行存储
				//其数组的下标+1代表第几个皇后,放在第几行,数组元素是第几列
				public class EightQenue {
					int MaxSize = 8;
					int []arr = new int[MaxSize];  // 因为要共用的,所以定义在前面
				     
					public static void main(String[] args) {
						EightQenue qt = new EightQenue();
						qt.check(0);
				
					}
					//编写放置第n个皇后的方法
					public void check(int n) {
						if(n>= 8) {
							//前面8个位置已经摆放好
							show();
							return;
						}
						//下面进行位置摆放,不停地在每一列试放
						for (int i = 0; i < MaxSize; i++) {
							arr[n] = i; //摆放位置
							if(Judge(n)) { //摆放第n个不冲突
								check(n+1);//摆放下一个,直到摆放完
							}
							//若冲突的话就会进行回溯,i++,放下一列继续判断。
						}
					}
				     //判断皇后冲突的方法,使得同列同斜线的位置不摆放,同行的话肯定是不存在,由数组的下标决定了
					public boolean Judge(int n) { //	n表示第n个皇后
						for(int i=0;i<n;i++) { //判断与前面摆法是否有冲突
							//arr[i]==arr[n]判断是否在同一列,列数存放在数组的元素值
							//Math.abs(n-i)==Math.abs(arr[n]-arr[i])
							//n-i代表行的差值 与arr[n]-arr[i]代表列的差值,
				            //若行的差值与列的差值相同,则两位置必定是同一斜线上
							if(arr[i]==arr[n] || Math.abs(n-i)==Math.abs(arr[n]-arr[i])) {
								return false;
								
							}	
							
						}
						
						return true;
					}
					
					//将皇后排放的位置进行打印
					public void show() {
						for (int i = 0; i < MaxSize; i++) {
							System.out.print(arr[i]+1+" ");
						}//这是一种解法
						System.out.println();
					}
				}
  • 结果:采用一个一维数组进行存储,数组的下标代表位置的行数-1,数组元素的值为位置的列数-1 一共是72种,
    很明显,当找完第一个解法时,会继续进行遍历,光是冲突判断就15720次,其效率不高:
    在这里插入图片描述
    力扣40题:组合总和II,这里要求元素不能重复使用,且不能有相同的排列出现。
    在这里插入图片描述

  • 与39不同的是,这里每次返回的索引一定是指向下一个,使得每个元素不被重复使用,
    这里没有TreeSet排序,利用数组排序来避免出现重复的排列,
    当然也可以一开始就进行排序,后面的结果肯定没有重复:这里采用和的形式判断
    
class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> lst = new ArrayList<>();
        int[] arr = candidates;
        search(arr,0,lst,0,target);//目标数组,当前索引,子列表集合,当前和,目标和
        return list;
    }
    public void search(int[] arr,int index,List<Integer> lst,int sum,int target){
      if(sum>target){
          return;
      }
      if(sum == target){
         List<Integer> temp = new ArrayList<>();
         //HashSet<Integer> hs = new HashSet<>();
         //集合转数组,数组转集合
         Integer[] brr = new Integer[lst.size()];
         lst.toArray(brr);
         Arrays.sort(brr);
         temp = Arrays.asList(brr);
         
         if(list.contains(temp)){ //判断大集合是否有重复,重复跳过
             return;
         }
         list.add(temp);
         return;
         
      }
      for(int i=index;i<arr.length;i++){
          if(arr[i]>target){continue;}
          //if(lst.contains(arr[i])){ continue;}//判断是否元素重复
        lst.add(arr[i]);
        search(arr,i+1,lst,sum+arr[i],target); //避免原来用过的元素重复使用
        lst.remove(lst.size()-1);//根据下标去掉最后一个元素,最后一个(当前元素不满足时跳过)
          
      }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值