一、递归的介绍

1、递归应用场景

QQ截图20200812100845.jpg

2、递归的概念

QQ截图20200812100903.jpg

3、递归调用机制(案例+图示)


案例介绍

QQ截图20200812100957.jpg

  1. package com.mjj.test;
  2. public class UsualExer {
  3. //打印问题
  4. public static void test(int n) {
  5. if(n>2) {
  6. test(n-1);
  7. }
  8. System.out.println("n = "+n);
  9. }
  10. //阶乘问题
  11. public static int factorial(int n) {
  12. if(n==1) {
  13. return 1;
  14. }
  15. return factorial(n-1)*n;
  16. }
  17. public static void main(String[] args) {
  18. //测试打印问题
  19. System.out.println("测试打印问题:");
  20. test(4);
  21. System.out.println();
  22. //测试阶乘问题 1*2*3*4=24
  23. System.out.println("4的阶乘处理结果为:"+factorial(4));
  24. }
  25. }

运行结果:
QQ截图20200812102107.jpg

图解说明

函数调用使用到的机制是栈,每次先执行栈顶的事件。
QQ截图20200812100334.jpg

4、递归能解决什么样的问题

QQ截图20200812103318.jpg

5、递归需要遵守的重要规则

QQ截图20200812103407.jpg


二、递归-迷宫问题

源代码:MazeExer.java

题目说明

有8行7列的二维数组,中间绿色的是围墙,从(1,1)开始,终点为(6,5),在对应走的路径上设置为2表示走通。
QQ截图20200812122323.jpg

代码演示

说明:走到(6,5)位置即为走通迷宫,对应数组位置上坐标0代表没走过,1代表是围墙,2代表走过,3表示已走过没有路

  1. package Recursion;
  2. public class MazeExer {
  3. //将二维数组打印
  4. public static void printArr(int arr[][]) {
  5. for(int i=0;i<arr.length;i++) {
  6. for(int j=0;j<arr[i].length;j++) {
  7. System.out.print(arr[i][j]+" ");
  8. }
  9. System.out.println();
  10. }
  11. }
  12. //进行迷宫
  13. /**
  14. *
  15. * @Description
  16. * @auther changlu
  17. * @date Aug 12, 202011:55:22 AM
  18. * @param arr 使用二维数组作为迷宫
  19. * @param i 是当前位置的y轴
  20. * @param j 是当前位置的x轴
  21. * @return 返回true或false 代表该路是否可以走通
  22. */
  23. public static boolean setWay(int arr[][],int i,int j) {
  24. //坐标0代表没走过,1代表是围墙,2代表走过,3表示已走过没有路
  25. //设置(1,1)起点,(6,5)为终点
  26. if(arr[6][5] == 2) {
  27. return true;
  28. }else {
  29. //当前位置为0时
  30. if(arr[i][j] == 0) {
  31. arr[i][j] = 2;//表示已经走过
  32. //可以往4个方向走
  33. if(setWay(arr, i+1, j)) {//这里往下走
  34. return true;
  35. }else if(setWay(arr, i, j+1)) {//这里往右走
  36. return true;
  37. }else if(setWay(arr, i-1, j)) {//往上走
  38. return true;
  39. }else if(setWay(arr, i, j-1)) {//往左走
  40. return true;
  41. }else {
  42. //上下左右路都走不通
  43. arr[i][j] = 3;//标识这里已经走过并且走不通
  44. return false;
  45. }
  46. }else {
  47. //当前位置上为1,2,3 就都不走
  48. return false;
  49. }
  50. }
  51. }
  52. }

测试:

  1. public class MazeExer {
  2. public static void main(String[] args) {
  3. //创建一个8行7列的迷宫
  4. int[][] arr = new int[8][7];
  5. //给迷宫外围一圈赋值围墙
  6. for(int i=0;i<=6;i++) {
  7. arr[0][i] = 1;
  8. arr[7][i] = 1;
  9. }
  10. for(int i=0;i<=7;i++) {
  11. arr[i][0] = 1;
  12. arr[i][6] = 1;
  13. }
  14. //额外作墙板
  15. arr[3][1] = 1;
  16. arr[3][2] = 1;
  17. //第一次打印
  18. printArr(arr);
  19. boolean flag = setWay(arr, 1, 1);
  20. System.out.println("是否能走通:"+flag);
  21. //打印迷宫
  22. System.out.println("\n走通后的迷宫:");
  23. printArr(arr);
  24. }
  25. }

运行结果:
QQ截图20200812122753.jpg


三、递归-八皇后问题(回溯算法)

1、八皇后问题介绍

QQ截图20200813100315.jpgQQ截图20200813100328.jpg

2、八皇后问题算法思路分析

QQ截图20200813100418.jpgQQ截图20200813100426.jpg

代码演示(说明)

属性说明:这里棋盘是否一个一维数组来进行代替,如arr[0] = 2,这个下标0代表是第一行,2代表的是这行上的第三个,使用count来计算方法的总数。下标为行数,值为行中的位置。

方法说明:这里总共 三个方法,打印数组、检查对应行与之前的棋盘有同列以及同斜线的、递归回溯求解一维数组。
printArr():使用Arrays打印数组
check(int n) :传入下标,通过一维数组来查看是否不符合同列以及同斜线情况,符合返回true,不符合返回false
getQueue8(int n):通过递归来对棋盘上所有情况进行检查选择,中途会使用check()进行检查

时间复杂度为:8^8,整体算法会有回溯,从而暴力解决获取所有情况。

  1. package StackExer;
  2. import java.util.Arrays;
  3. public class Queue8 {
  4. //使用一维数组来代替八皇后棋盘
  5. //下标为第几行,对应的值这一行的第几个
  6. private int max = 8;
  7. private int [] array = new int[8];
  8. private static int count;
  9. //进行八皇后棋盘获取,n为行
  10. public void getQueue8(int n) {
  11. //如果n为8说明,已经成功获取八皇后棋盘摆棋方式
  12. if(n == max) {
  13. printArr();
  14. return;
  15. }
  16. //进行遍历
  17. for(int i=0;i<max;i++) {
  18. //填充第n列上的值为i,i代表第几个位置
  19. array[n] = i;
  20. //判断是否有出现冲突的情况
  21. if(check(n)) {
  22. //没有冲突情况进行递归
  23. getQueue8(n+1);
  24. }
  25. }
  26. }
  27. //创建一个检查方法,传入的n是第几行
  28. public boolean check(int n) {
  29. //判断是否存在同一列以及同一斜线的
  30. for(int i=0;i<n;i++) {
  31. if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[i]-array[n])) {
  32. return false;
  33. }
  34. }
  35. //没有同一列以及同一斜线的返回true
  36. return true;
  37. }
  38. //用来打印一维数组
  39. public void printArr() {
  40. count++;
  41. System.out.println(Arrays.toString(array));
  42. }
  43. }

测试方法:

  1. public class Queue8 {
  2. public static void main(String[] args) {
  3. Queue8 queue8 = new Queue8();
  4. queue8.getQueue8(0);
  5. System.out.printf("摆棋方式有%d种",count);
  6. }
  7. }

运行结果:
QQ截图20200813121232.jpg


四、全排列

参考文章:轻松理解全排列算法的递归解法
**

详细过程(二部分)


① 入门,并不是求解全排列,而是列举四种全排列形式

image.png

  1. public class Main {
  2. public static void main(String[] args) {
  3. int[] a = {1,2,3,4};
  4. quanpailie(a,0,a.length);
  5. }
  6. private static void quanpailie(int[] a, int cur, int length) {
  7. for(int i = cur;i<length;i++){
  8. swap(a, cur,i );
  9. System.out.println(Arrays.toString(a));
  10. swap(a, i, cur);//将先前替换的撤销
  11. }
  12. }
  13. public static void swap(int[]a,int cur,int i){
  14. int temp = a[cur];
  15. a[cur] = a[i];
  16. a[i] = temp;
  17. }
  18. }

运行结果:
image.png


② 使用递归求解全排列

添加出口条件,来进行输出

  1. package exer;
  2. import java.util.*;
  3. public class Main {
  4. public static void main(String[] args) {
  5. int[] a = {1,2,3,4};
  6. quanpailie(a,0,a.length);
  7. }
  8. //进行全排列
  9. private static void quanpailie(int[] a, int cur, int length) {
  10. //输出列举的情况
  11. if(cur == length){
  12. System.out.println(Arrays.toString(a));
  13. }
  14. for(int i = cur;i<length;i++){
  15. swap(a, cur,i );
  16. //System.out.println(Arrays.toString(a));
  17. quanpailie(a, cur+1, length);//分解为子问题进行递归
  18. swap(a, i, cur);
  19. }
  20. }
  21. //替换操作
  22. public static void swap(int[]a,int cur,int i){
  23. int temp = a[cur];
  24. a[cur] = a[i];
  25. a[i] = temp;
  26. }
  27. }

image.png 包含24种情况

—————————————————————————————————————————————————————————————
**

解决全排列重复问题(忽略重复的全排列)

对于全排列如果我们不想要重复的排列数据时,如下数组中包含重复的元素
image.pngimage.png 会出现重复的情况
**

添加一个查询方法:
cur是数组当前从cur位置开始,i是对应要调换的数,我们只需要判断在[cur,i)之间是否与在数组中i位置的数据相同,那么我们就返回false

  1. //判断是否出现重复的情况
  2. public static boolean isRepeat(int[] b,int cur,int i){
  3. //因为我们是要将cur与i来进行调换,如果之前重复的话就不需要进行往下递归来生成对应值
  4. for(int j=cur;j<i;j++){
  5. if(b[j] == b[i])
  6. return false;
  7. }
  8. return true;
  9. }

完整如下:

  1. public class Main {
  2. public static void main(String[] args) {
  3. int[] a = {1,2,2,3,4};
  4. quanpailie(a,0,a.length);
  5. }
  6. //进行全排列
  7. private static void quanpailie(int[] a, int cur, int length) {
  8. //输出列举的情况
  9. if(cur == length){
  10. System.out.println(Arrays.toString(a));
  11. }
  12. for(int i = cur;i<length;i++){
  13. //判断是否与之前的有重复元素的情况
  14. if(!isRepeat(a, cur, i))
  15. return;
  16. swap(a, cur,i );
  17. //System.out.println(Arrays.toString(a));
  18. quanpailie(a, cur+1, length);//分解为子问题进行递归
  19. swap(a, i, cur);
  20. }
  21. }
  22. //替换操作
  23. public static void swap(int[]a,int cur,int i){
  24. int temp = a[cur];
  25. a[cur] = a[i];
  26. a[i] = temp;
  27. }
  28. //判断是否出现重复的情况
  29. public static boolean isRepeat(int[] a,int cur,int i){
  30. //因为我们是要将cur与i来进行调换,如果之前重复的话就不需要进行往下递归来生成对应值
  31. for(int j=cur;j<i;j++){
  32. if(a[j] == a[i])
  33. return false;
  34. }
  35. return true;
  36. }
  37. }

image.png 就没有重复情况了


整理者:长路 时间:2020/8/12、2020/11/13