一、什么是递归?

自身不断地循环自身。
最简单的递归:
计算阶乘n! = 1 * 2 * 3 * 4 * …… * (n-1) * n
循环实现:

  1. public static int factorial(int n) {
  2. int factorial = 1;
  3. for (int i = 1; i <= n; i++) {
  4. factorial *= i;
  5. }
  6. return factorial;
  7. }

递归实现:

  1. public static int factorial(int n) {
  2. //#1. 当 n = 1 时,递归结束
  3. if (n == 1) {
  4. return 1;
  5. }
  6. //#2. 把 factorial(n - 1) 的结果和 n 相乘,剩下的交给 factorial(n - 1) 来解决。
  7. return n * factorial(n - 1);
  8. }

二、如何实现递归?

1.基准条件(结束条件,没有这个条件代码将出现死循环)
2.递归公式(递归公式总是描述自己和下一个递归函数直接的递进关系)
image.png
练习题1.
image.png

答案:

  1. package com.youkeda;
  2. public class Recursive {
  3. public static int fibonacci(int n) {
  4. if (n == 0 || n == 1) {
  5. return n;
  6. }
  7. return fibonacci(n - 1) + fibonacci(n - 2);
  8. }
  9. public static void main(String[] args) {
  10. System.out.println("fibonacci(1) = " + fibonacci(1));
  11. System.out.println("fibonacci(3) = " + fibonacci(3));
  12. System.out.println("fibonacci(10) = " + fibonacci(10));
  13. }
  14. }

练习题2
image.png
image.png

答案:

  1. package com.youkeda;
  2. import java.util.Arrays;
  3. public class Recursive {
  4. // 查找应该插入的索引位置
  5. public static int searchIndex(int[] array, int left, int right, int aim) {
  6. // 循环查找节点位置
  7. if (left < right) {
  8. int middle = (left + right) / 2;
  9. int value = array[middle];
  10. if (value < aim) {
  11. // left = middle + 1;
  12. return searchIndex(array, middle + 1, right, aim);
  13. } else {
  14. // right = middle - 1;
  15. return searchIndex(array, left, right-1, aim);
  16. }
  17. }
  18. // #1. 如果最终元素仍然大于目标元素,则将索引位置往左边移动一个
  19. if (array[left] > aim) {
  20. return left - 1;
  21. }
  22. // 否则就是当前位置
  23. return left;
  24. }
  25. // 插入排序
  26. public static void insertSort(int[] array) {
  27. // 从倒数第二位开始,遍历到底0位,遍历 N-1 次
  28. for (int i = array.length - 2; i >= 0; i--) {
  29. // 存储当前抽离的元素
  30. int temp = array[i];
  31. //获取应该插入的索引位置
  32. int index = searchIndex(array, i + 1, array.length - 1, temp);
  33. // 根据插入的索引位置,进行数组的移动和插入
  34. int j = i + 1;
  35. //对数组进行移动
  36. while (j <= index) {
  37. array[j - 1] = array[j];
  38. j++;
  39. }
  40. //给数组末尾赋值
  41. array[j - 1] = temp;
  42. }
  43. }
  44. public static void main(String[] args) {
  45. int[] array = {9, 2, 4, 7, 5, 3};
  46. // Arrays.toString 可以方便打印数组内容
  47. System.out.println(Arrays.toString(array));
  48. insertSort(array);
  49. System.out.println(Arrays.toString(array));
  50. }
  51. }

三、分而治之思想-归并排序

image.pngimage.pngimage.pngimage.png
image.pngimage.pngimage.png

  1. // 归并排序,返回排好序的数组
  2. public static int[] mergeSort(int[] array) {
  3. // 为了方便查看结果,我们将每个数组进行打印
  4. System.out.println(Arrays.toString(array));
  5. if (array.length == 1) {
  6. return array;
  7. }
  8. int middle = array.length / 2;
  9. // #1. 处理 0 到 middle 左侧数组部分
  10. int[] left = mergeSort(subArray(array, 0, middle));
  11. // #2. 处理 middle 到 array.length 右侧数组部分
  12. int[] right = mergeSort(subArray(array, middle, array.length));
  13. // TODO处理合并问题
  14. return array;
  15. }

大家重点理解上面#1 #2注释的代码,理解创建子数组,然后递归分治的逻辑(我们暂时忽略合并排序的逻辑)。
image.png
image.png
递归 - 图14
image.png递归 - 图16
image.png递归 - 图18image.png
代码实现:

  1. package com.youkeda;
  2. import java.util.Arrays;
  3. public class Sort {
  4. // 归并排序
  5. public static int[] mergeSort(int[] array) {
  6. // 为了方便查看结果,我们将每个数组进行打印
  7. if (array.length == 1) {
  8. return array;
  9. }
  10. int middle = array.length / 2;
  11. // 处理 0 到 middle 左侧数组部分
  12. int[] left = mergeSort(subArray(array, 0, middle));
  13. // 处理 middle 到 array.length 右侧数组部分
  14. int[] right = mergeSort(subArray(array, middle, array.length));
  15. // TODO处理合并问题
  16. int l = 0;
  17. int r = 0;
  18. int index = 0;
  19. // 依次比较左右两个数组
  20. while (l < left.length && r < right.length) {
  21. array[index] = Math.min(left[l], right[r]);
  22. index++;
  23. if (left[l] < right[r]) {
  24. l++;
  25. } else {
  26. r++;
  27. }
  28. }
  29. // 右侧数组已经遍历完成,左侧有剩余
  30. if (l < left.length) {
  31. for(int i = l; i < left.length; i++){
  32. array[index] = left[i];
  33. index++;
  34. }
  35. }
  36. // 左侧数组已经遍历完成,右侧有剩余
  37. if(r < right.length){
  38. for(int i = r; i < right.length; i++){
  39. array[index] = right[i];
  40. index++;
  41. }
  42. }
  43. return array;
  44. }
  45. // 拷贝原数组的部分内容,从 left 到 right
  46. public static int[] subArray(int[] source, int left, int right) {
  47. // 创建一个新数组
  48. int[] result = new int[right - left];
  49. // 依次赋值进去
  50. for (int i = left; i < right; i++) {
  51. result[i - left] = source[i];
  52. }
  53. return result;
  54. }
  55. public static void main(String[] args) {
  56. int[] array = {9, 2, 4, 7, 5, 3};
  57. // Arrays.toString 可以方便打印数组内容
  58. System.out.println("raw: " + Arrays.toString(array));
  59. int[] result = mergeSort(array);
  60. System.out.println("result: " + Arrays.toString(result));
  61. }
  62. }

四、分而治之思想-快速排序(重要)

快速排序使用分治法将序列分成两个较大和较小的子序列,然后递归的排序两个子序列。

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
  2. 分割:所有比基准值小的元素放在基准值的左边,所有比基准值大的元素放在基准值的右边,分割成两个子序列
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

image.png
image.pngimage.png
image.pngimage.pngimage.pngimage.png
image.pngimage.png
image.pngimage.pngimage.pngimage.png

  1. // 快速排序
  2. public static void quickSort(int[] array) {
  3. // 调用快速排序的核心,传入left,right
  4. quickSortCore(array, 0, array.length - 1);
  5. }
  6. // 快速排序的核心,同样也是递归函数
  7. public static void quickSortCore(int[] array, int left, int right) {
  8. // 递归基准条件,left >= right 即表示数组只有1个或者0个元素。
  9. if (left >= right) {
  10. return;
  11. }
  12. // 根据轴分区
  13. int pivotIndex = partition(array, left, right);
  14. // 递归调用左侧和右侧数组分区
  15. quickSortCore(array, left, pivotIndex - 1);
  16. quickSortCore(array, pivotIndex + 1, right);
  17. }
  18. // 对数组进行分区,并返回当前轴所在的位置
  19. public static int partition(int[] array, int left, int right) {
  20. }

image.png
答案:

  1. package com.youkeda;
  2. import java.util.Arrays;
  3. public class QuickSort {
  4. // 快速排序
  5. public static void quickSort(int[] array) {
  6. // 调用快速排序的核心,传入left,right
  7. quickSortCore(array, 0, array.length - 1);
  8. }
  9. // 快速排序的核心,同样也是递归函数
  10. public static void quickSortCore(int[] array, int left, int right) {
  11. // 递归基准条件,left >= right 即表示数组只有1个或者0个元素。
  12. if (left >= right) {
  13. return;
  14. }
  15. // 根据轴分区
  16. int pivotIndex = partition(array, left, right);
  17. // 递归调用左侧和右侧数组分区
  18. quickSortCore(array, left, pivotIndex - 1);
  19. quickSortCore(array, pivotIndex + 1, right);
  20. }
  21. // 对数组进行分区,并返回当前轴所在的位置
  22. public static int partition(int[] array, int left, int right) {
  23. int pivot = array[right];
  24. int leftIndex = left;
  25. int rightIndex = right - 1;
  26. while (true) {
  27. // 左指针移动
  28. while (array[leftIndex] <= pivot && leftIndex < right) {
  29. leftIndex++;
  30. }
  31. // 右指针移动
  32. while (array[rightIndex] >= pivot && rightIndex > 0) {
  33. rightIndex--;
  34. }
  35. if (leftIndex >= rightIndex) {
  36. break;
  37. } else {
  38. swap(array, leftIndex, rightIndex);
  39. }
  40. }
  41. swap(array, leftIndex, right);
  42. return leftIndex;
  43. }
  44. public static void swap(int[] array, int index1, int index2) {
  45. int temp = array[index1];
  46. array[index1] = array[index2];
  47. array[index2] = temp;
  48. }
  49. public static void main(String[] args) {
  50. int[] array = {9, 2, 4, 7, 5, 3};
  51. // Arrays.toString 可以方便打印数组内容
  52. System.out.println("raw: " + Arrays.toString(array));
  53. quickSort(array);
  54. System.out.println("result: " + Arrays.toString(array));
  55. }
  56. }

五、快速排序应用-快速选择

image.pngimage.pngimage.pngimage.png
答案:

  1. package com.youkeda;
  2. import java.util.Arrays;
  3. public class QuickSort {
  4. // 快速选择,返回选中的元素
  5. public static int quickFind(int[] array, int aim) {
  6. // 调用快速选择的核心,传入left,right
  7. return quickFindCore(array, aim, 0, array.length - 1);
  8. }
  9. // 快速选择的核心,同样也是递归函数
  10. public static int quickFindCore(int[] array, int aim, int left, int right) {
  11. // 递归基准条件,left >= right 即表示数组只有1个或者0个元素,返回当前的元素
  12. if (left >= right) {
  13. return array[left];
  14. }
  15. // 根据轴分区
  16. int pivotIndex = partition(array, left, right);
  17. // 根据 aim 确定继续递归的方向
  18. if (pivotIndex > aim) {
  19. return quickFindCore(array, aim, left, pivotIndex - 1);
  20. } else if (pivotIndex < aim) {
  21. return quickFindCore(array, aim, pivotIndex + 1, right);
  22. } else {
  23. return array[pivotIndex];
  24. }
  25. }
  26. // 对数组进行分区,并返回当前轴所在的位置
  27. public static int partition(int[] array, int left, int right) {
  28. int pivot = array[right];
  29. int leftIndex = left;
  30. int rightIndex = right - 1;
  31. while (true) {
  32. // 左指针移动
  33. while (array[leftIndex] < pivot && leftIndex < right) {
  34. leftIndex++;
  35. }
  36. // 右指针移动
  37. while (array[rightIndex] > pivot && rightIndex > 0) {
  38. rightIndex--;
  39. }
  40. if (leftIndex >= rightIndex) {
  41. break;
  42. } else {
  43. swap(array, leftIndex, rightIndex);
  44. }
  45. }
  46. swap(array, leftIndex, right);
  47. return leftIndex;
  48. }
  49. public static void swap(int[] array, int index1, int index2) {
  50. int temp = array[index1];
  51. array[index1] = array[index2];
  52. array[index2] = temp;
  53. }
  54. public static void main(String[] args) {
  55. int[] array = {72, 77, 48, 17, 71, 2, 25, 97, 82, 5, 2, 18, 15, 57, 7, 48, 93, 47, 38, 74, 18, 93, 98, 41, 54, 4, 47, 4, 63, 76};
  56. System.out.println("raw: " + Arrays.toString(array));
  57. // 目标是倒数第 6 个元素
  58. int result = quickFind(array, array.length - 6);
  59. System.out.println("result: " + result);
  60. }
  61. }