一、什么是递归?
自身不断地循环自身。
最简单的递归:
计算阶乘n! = 1 * 2 * 3 * 4 * …… * (n-1) * n
循环实现:
public static int factorial(int n) {
int factorial = 1;
for (int i = 1; i <= n; i++) {
factorial *= i;
}
return factorial;
}
递归实现:
public static int factorial(int n) {
//#1. 当 n = 1 时,递归结束
if (n == 1) {
return 1;
}
//#2. 把 factorial(n - 1) 的结果和 n 相乘,剩下的交给 factorial(n - 1) 来解决。
return n * factorial(n - 1);
}
二、如何实现递归?
1.基准条件(结束条件,没有这个条件代码将出现死循环)
2.递归公式(递归公式总是描述自己和下一个递归函数直接的递进关系)
练习题1.
答案:
package com.youkeda;
public class Recursive {
public static int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println("fibonacci(1) = " + fibonacci(1));
System.out.println("fibonacci(3) = " + fibonacci(3));
System.out.println("fibonacci(10) = " + fibonacci(10));
}
}
练习题2
答案:
package com.youkeda;
import java.util.Arrays;
public class Recursive {
// 查找应该插入的索引位置
public static int searchIndex(int[] array, int left, int right, int aim) {
// 循环查找节点位置
if (left < right) {
int middle = (left + right) / 2;
int value = array[middle];
if (value < aim) {
// left = middle + 1;
return searchIndex(array, middle + 1, right, aim);
} else {
// right = middle - 1;
return searchIndex(array, left, right-1, aim);
}
}
// #1. 如果最终元素仍然大于目标元素,则将索引位置往左边移动一个
if (array[left] > aim) {
return left - 1;
}
// 否则就是当前位置
return left;
}
// 插入排序
public static void insertSort(int[] array) {
// 从倒数第二位开始,遍历到底0位,遍历 N-1 次
for (int i = array.length - 2; i >= 0; i--) {
// 存储当前抽离的元素
int temp = array[i];
//获取应该插入的索引位置
int index = searchIndex(array, i + 1, array.length - 1, temp);
// 根据插入的索引位置,进行数组的移动和插入
int j = i + 1;
//对数组进行移动
while (j <= index) {
array[j - 1] = array[j];
j++;
}
//给数组末尾赋值
array[j - 1] = temp;
}
}
public static void main(String[] args) {
int[] array = {9, 2, 4, 7, 5, 3};
// Arrays.toString 可以方便打印数组内容
System.out.println(Arrays.toString(array));
insertSort(array);
System.out.println(Arrays.toString(array));
}
}
三、分而治之思想-归并排序
// 归并排序,返回排好序的数组
public static int[] mergeSort(int[] array) {
// 为了方便查看结果,我们将每个数组进行打印
System.out.println(Arrays.toString(array));
if (array.length == 1) {
return array;
}
int middle = array.length / 2;
// #1. 处理 0 到 middle 左侧数组部分
int[] left = mergeSort(subArray(array, 0, middle));
// #2. 处理 middle 到 array.length 右侧数组部分
int[] right = mergeSort(subArray(array, middle, array.length));
// TODO处理合并问题
return array;
}
大家重点理解上面#1 #2注释的代码,理解创建子数组,然后递归分治的逻辑(我们暂时忽略合并排序的逻辑)。
代码实现:
package com.youkeda;
import java.util.Arrays;
public class Sort {
// 归并排序
public static int[] mergeSort(int[] array) {
// 为了方便查看结果,我们将每个数组进行打印
if (array.length == 1) {
return array;
}
int middle = array.length / 2;
// 处理 0 到 middle 左侧数组部分
int[] left = mergeSort(subArray(array, 0, middle));
// 处理 middle 到 array.length 右侧数组部分
int[] right = mergeSort(subArray(array, middle, array.length));
// TODO处理合并问题
int l = 0;
int r = 0;
int index = 0;
// 依次比较左右两个数组
while (l < left.length && r < right.length) {
array[index] = Math.min(left[l], right[r]);
index++;
if (left[l] < right[r]) {
l++;
} else {
r++;
}
}
// 右侧数组已经遍历完成,左侧有剩余
if (l < left.length) {
for(int i = l; i < left.length; i++){
array[index] = left[i];
index++;
}
}
// 左侧数组已经遍历完成,右侧有剩余
if(r < right.length){
for(int i = r; i < right.length; i++){
array[index] = right[i];
index++;
}
}
return array;
}
// 拷贝原数组的部分内容,从 left 到 right
public static int[] subArray(int[] source, int left, int right) {
// 创建一个新数组
int[] result = new int[right - left];
// 依次赋值进去
for (int i = left; i < right; i++) {
result[i - left] = source[i];
}
return result;
}
public static void main(String[] args) {
int[] array = {9, 2, 4, 7, 5, 3};
// Arrays.toString 可以方便打印数组内容
System.out.println("raw: " + Arrays.toString(array));
int[] result = mergeSort(array);
System.out.println("result: " + Arrays.toString(result));
}
}
四、分而治之思想-快速排序(重要)
快速排序使用分治法将序列分成两个较大和较小的子序列,然后递归的排序两个子序列。
- 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
- 分割:所有比基准值小的元素放在基准值的左边,所有比基准值大的元素放在基准值的右边,分割成两个子序列
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
// 快速排序
public static void quickSort(int[] array) {
// 调用快速排序的核心,传入left,right
quickSortCore(array, 0, array.length - 1);
}
// 快速排序的核心,同样也是递归函数
public static void quickSortCore(int[] array, int left, int right) {
// 递归基准条件,left >= right 即表示数组只有1个或者0个元素。
if (left >= right) {
return;
}
// 根据轴分区
int pivotIndex = partition(array, left, right);
// 递归调用左侧和右侧数组分区
quickSortCore(array, left, pivotIndex - 1);
quickSortCore(array, pivotIndex + 1, right);
}
// 对数组进行分区,并返回当前轴所在的位置
public static int partition(int[] array, int left, int right) {
}
答案:
package com.youkeda;
import java.util.Arrays;
public class QuickSort {
// 快速排序
public static void quickSort(int[] array) {
// 调用快速排序的核心,传入left,right
quickSortCore(array, 0, array.length - 1);
}
// 快速排序的核心,同样也是递归函数
public static void quickSortCore(int[] array, int left, int right) {
// 递归基准条件,left >= right 即表示数组只有1个或者0个元素。
if (left >= right) {
return;
}
// 根据轴分区
int pivotIndex = partition(array, left, right);
// 递归调用左侧和右侧数组分区
quickSortCore(array, left, pivotIndex - 1);
quickSortCore(array, pivotIndex + 1, right);
}
// 对数组进行分区,并返回当前轴所在的位置
public static int partition(int[] array, int left, int right) {
int pivot = array[right];
int leftIndex = left;
int rightIndex = right - 1;
while (true) {
// 左指针移动
while (array[leftIndex] <= pivot && leftIndex < right) {
leftIndex++;
}
// 右指针移动
while (array[rightIndex] >= pivot && rightIndex > 0) {
rightIndex--;
}
if (leftIndex >= rightIndex) {
break;
} else {
swap(array, leftIndex, rightIndex);
}
}
swap(array, leftIndex, right);
return leftIndex;
}
public static void swap(int[] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
public static void main(String[] args) {
int[] array = {9, 2, 4, 7, 5, 3};
// Arrays.toString 可以方便打印数组内容
System.out.println("raw: " + Arrays.toString(array));
quickSort(array);
System.out.println("result: " + Arrays.toString(array));
}
}
五、快速排序应用-快速选择
答案:
package com.youkeda;
import java.util.Arrays;
public class QuickSort {
// 快速选择,返回选中的元素
public static int quickFind(int[] array, int aim) {
// 调用快速选择的核心,传入left,right
return quickFindCore(array, aim, 0, array.length - 1);
}
// 快速选择的核心,同样也是递归函数
public static int quickFindCore(int[] array, int aim, int left, int right) {
// 递归基准条件,left >= right 即表示数组只有1个或者0个元素,返回当前的元素
if (left >= right) {
return array[left];
}
// 根据轴分区
int pivotIndex = partition(array, left, right);
// 根据 aim 确定继续递归的方向
if (pivotIndex > aim) {
return quickFindCore(array, aim, left, pivotIndex - 1);
} else if (pivotIndex < aim) {
return quickFindCore(array, aim, pivotIndex + 1, right);
} else {
return array[pivotIndex];
}
}
// 对数组进行分区,并返回当前轴所在的位置
public static int partition(int[] array, int left, int right) {
int pivot = array[right];
int leftIndex = left;
int rightIndex = right - 1;
while (true) {
// 左指针移动
while (array[leftIndex] < pivot && leftIndex < right) {
leftIndex++;
}
// 右指针移动
while (array[rightIndex] > pivot && rightIndex > 0) {
rightIndex--;
}
if (leftIndex >= rightIndex) {
break;
} else {
swap(array, leftIndex, rightIndex);
}
}
swap(array, leftIndex, right);
return leftIndex;
}
public static void swap(int[] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
public static void main(String[] args) {
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};
System.out.println("raw: " + Arrays.toString(array));
// 目标是倒数第 6 个元素
int result = quickFind(array, array.length - 6);
System.out.println("result: " + result);
}
}