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();
}
}
}