递归思想(分治策略,回溯算法)

1.递归的概念。

    递归是一种针对使用简单的循环难以编程实现的问題,提供优雅解决方案的技术。简单地说,递归是将一个很复杂的问题,划分为很多的小问题,从小问题开始解决,然后将小问题的结果进行返回的方法。对于for循环来说,可以写但是for循环的次数和量特别大(说到底就是代码量十分庞大),甚至说for循环无法写出来。

    就按电脑文件夹来说,文件夹里边可能有文本,也可能有文件夹,文件夹里边可能还要文件夹,持续循环下去,这便是递归。

2.前进段和分治算法。

  分治算法:

      和咋们在前边说的一样,当需要解决某些问题时,由于问题本身需要解决的数据十分庞大,这个时候我们就可以将该问题划分为许多个小问题,小问题再划分为更小问题,直到找到可以解决极小问题的解法为止。这个概念便是分治算法。

  分治算法整体思想:

  1、将一个大问题,划分为k个小问题,如果k个小问题还不够求解,那么继续划分,直到出现可以求解的小问题为止。

  2、对所有的小问题求出解后,返回到上一层继续对上层的问题进行求解。

  3、分治算法的思想便是如此,逐一求解,最终求出大问题的解。

  Divide:将问题的规模变小

  Conquer:递归的处理小规模问题(只需递归调用即可)

  Combine:将小规模问题的解合并为原始问题的解

  4、递归和分治的区别。

  递归是算法的实现方式,分治是算法的设计思想。

  迭代是不断地循环过程,递归式不断地调用自身。

  5、递归存在的弊端。

  递归解决问题的方式是在不断的进行调用,函数不断地进栈,这也导致的内存中栈的空间大量使用,形成极大浪费。

3.分治算法的应用图片范例——二分查找

给定已按升序排好序的n个元素arr[0~(n-1)],现要在这n个元素中找出以特定元素e。

    分析:

    1、该问题的规模缩小到一定的程度就可以容易地解决。

    2、该问题可以分解为若干个规模较小的相同问题。

    3、分解出的子问题的解可以合并为原问题的解。

    4、分解出的各个子问题是相互独立的。

  代码实现: 

package com.oupeng.p7递归;

public class BinarySearchRecursion {
	public static void main(String[] args) {
		int[] arr={1,2,3,4,5,6,7,8,9,10,11,12};
		int key=11;
		int index=binarySearch(arr,0,arr.length-1,key);
		System.out.println(index);
	}
	public static int binarySearch(int[] arr, int min, int max, int key) {
	    int mid=(min+max)/2;
	    if(arr[mid]==key){
	    	return mid;
	    }
	    if(min>max){
	    	return -1;
	    }
	    if(arr[mid]>key){
	    	return binarySearch(arr,min,mid-1,key);
	    }else{
	    	return binarySearch(arr,min+1,max,key);
	    }
	}
}

 

4.递归范例——分治思想。

  经典范例1:1+2+3+4.......+(n-1)......+n

  咋们拿这个范例缩小点,比如拿前100个数字和来说吧。100个数字和可以划分为100+99个数字和,99个数字和可以划分为99+98个数字和,一直递归下去便是1个数字的和。

代码实现:

package com.oupeng.p7递归;

public class RecursionDemo01 {
	//StackOverflowError 栈溢出错误
	public static void main(String[] args){
		System.out.println(f(100));
	}
	public static int f(int n){
		if(n==1){
			return 1;          //最终的小问题
		}else{
			return f(n-1)+n;   //调用函数
		}
	}
}

  经典范例2:阶乘函数

  阶乘函数的思想,差不多和n项和类型差不多。可以缩小范围,比如说10!的结果,10!=10*9!,9!=9*8!,......1!=1,0!=1。

 

  代码实现:

package com.oupeng.p7递归;

public class RecursionDemo02 {

	//StackOverflowError 栈溢出错误
	public static void main(String[] args){
		System.out.println(f(10));
	}
	public static long f(long n){
		if(n==1||n==0){
			return 1;
		}else{
			return n*f(n-1);
		}
	}
}

   经典范例3:斐波那契数列

  fibo:1 1 2 3 5 8 13 21......进行代码打印。

  由该函数规律可以发现,从第三个位置往后的数字等于前两个数字之和。

  代码实现:

package com.oupeng.p7递归;

public class RecursionDemo03 {
	//StackOverflowError 栈溢出错误
	public static void main(String[] args){
		for(int i=1;i<=3;i++){
			System.out.println(f(i));
		}
	}
	public static int f(int n){
		if(n==1||n==2){
			return 1;
		}else{
			return f(n-1)+f(n-2);
		}
	}
}

  经典范例4:汉诺塔 

  汉诺塔原本的要求是有三个底座x y z,要求将64个盘子从x移动到z。要求移动过程中,大盘子不能覆盖小盘子,每次只能移动一个盘子。

  64个盘子的移动操作量十分庞大,但我们把其缩小至3个盘子来说。首先,我们需要移动前2个盘子到底座y,才能将最x底层的盘子移动到z。这时候,前2个盘子在y,我们需要将前1个盘子移动到x,才能将y最底层的盘子移动到z。最后剩下一个盘子的时候,便是x移动到z的时候。

package com.oupeng.p7递归;

public class RecursionDemo04 {
	public static void main(String[] args) {
		move(3,"x","y","z");   //盘子数  xyz底座
	}
	//from mid to 是代号,不是固定的值
	public static void move(int i,String from,String mid,String to){//当所有盘子在x上的时候,也就是x为1,y为2,z为3
		if(i==1){
			System.out.println(from+"->"+to);//移完最底层上方所有盘子,剩下一个盘子怎么移动打印
		}else{
			move(i-1,from,to,mid);      //当所有盘子在x上的时候,也就是x为1,y为2,z为3  需要移动1位置最底层上方所有盘子
			System.out.println(from+"->"+to);
			move(i-1,mid,from,to);      //当所有盘子在y上时,也就是之前的y为1,x为2,z为3  需要移动1位置最底层上方所有盘子
		}
	}
}

5.返回段与回溯算法。

  回溯算法实际是一个类似枚举的搜索尝试过程,主要是在搜素过程中寻找问题的解,当发现已不满足求解条件时,就回溯返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以便到达目的。当搜索进行到某一步,发现该步无法进行下去时候,那么便退回上一步重新选择,这种走不下去便退回去的方法便是回溯法。像经典的八皇后问题就是回溯法最好的代码思想例子。同样的,像八皇后这样的规模庞大的问题的解法都可以使用回溯法。

递归和回溯的区别:递归是算法的实现方式,回溯是算法的设计思想。

6.递归范例——回溯思想。

  1、之前写的八皇后:https://mp.csdn.net/postedit/98089909

  2、数独题目。

  要求:同行,同列,同小九宫格不能有相同存在数字,用代码实现。

  1、递归函数solve()先传数组第一个位置的下标过去,然后判断该位置是否要赋值,再对应该赋值什么进行判断,就是题目的要求。

  2、题目要求的判断

    1:同行,遍历该行的所有元素,对传入的元素进行判断是否相等,相等返回true,也就是不能填写该值。

    2:同列,遍历该列的所有元素,对传入的元素进行判断是否相等,相等返回true,也就是不能填写该值。

    3:同小九宫格,先将九宫格的划分为三个区域【0~2】【3~5】【6~8】,再将划分为三个区域【0~2】【3~5】【6~8】。对传入的元素的行列进行判断,看是位于那个区域,然后限制行列的范围。相当缩小了一个长度99的数组为33的数组,然后两个for循环遍历缩小的33数组,寻找是否存在该元素,相等返回true,也就是不能填写该值。

  代码实现: 

package com.oupeng.p7递归;

import java.util.Scanner;

public class RecursionDemo03 {
	public static int[][] board=new int[9][9];
	public static void main(String[] args) {
		//1.读取一个数独
		@SuppressWarnings("resource")
		Scanner scanner=new Scanner(System.in);    //输入
		for(int i=0;i<9;i++){
			//"750090046"                          //输入已给的数字,未给按0处理
			String line=scanner.nextLine();        //咋们这个打印的数独可以当成一个字符串
			for(int j=0;j<9;j++){
				board[i][j]=Integer.parseInt(line.charAt(j)+"");
			}
		}
		//2.开始求解数独
		solve(0,0);
	}
	private static void solve(int row, int col) {
		if(row>=9){                                //下标0开始,row=9为第十行
			print();                               //打印解出后的数独
			System.exit(0);                        //程序立即结束
		}else{
			if(board[row][col]==0){
				for(int n=1;n<=9;n++){
					if(!isExist(row,col,n)){
						board[row][col]=n;
						solve(row+(col+1)/9,(col+1)%9);//需要把当前位置移动往下一位置,考虑特殊情况,当该位置位于该行最后一个位置时,需要跳到下一行第一个位置。
					}
					board[row][col]=0;
				}
			}else{
				solve(row+(col+1)/9,(col+1)%9);
			}
		}
	}
	private static boolean isExist(int row, int col, int n) {
		
		//先看行
		for(int c=0;c<9;c++){
			if(board[row][c]==n){
				return true;
			}
		}
		//再看列
		for(int r=0;r<9;r++){
			if(board[r][col]==n){
				return true;
			}
		}
		//后看小九宫
		int rowMin=0;
		int rowMax=0;
		int colMin=0;
		int colMax=0;
		//左上角 rowMin,colMin
		//右下角 rowMax,colMax
		if(row>=0&&row<=2){
			rowMin=0;
			rowMax=2;
		}
		if(row>=3&&row<=5){
			rowMin=3;
			rowMax=5;
		}
		if(row>=6&&row<=8){
			rowMin=6;
			rowMax=8;
		}
		if(col>=0&&col<=2){
			colMin=0;
			colMax=2;
		}
		if(col>=3&&col<=5){
			colMin=3;
			colMax=5;
		}
		if(col>=6&&col<=8){
			colMin=6;
			colMax=8;
		}
		
		for(int r=rowMin;r<=rowMax;r++){
			for(int c=colMin;c<=colMax;c++){
				if(board[r][c]==n){
					return true;
				}
			}
		}
		
		return false;
	}
	private static void print() {
		for(int i=0;i<9;i++){
			for(int j=0;j<9;j++){
				System.out.print(board[i][j]+" ");
			}
			System.out.println();
		}
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值