# 递归 master 公式
剖析递归行为和递归行为时间复杂度的估算
用递归方法找一个数组中的最大值,系统上到底是怎么做的?
# master 公式的使用
T(N) = a*T(N/b) + O(N^d)
T (N): 母问题的数据量是 N 级别的
T (N/b): 子问题都是 N/b 的规模
a: 子问题的调用次数
O (N^d): 除去子问题的调用之外剩下的过程
无论如何切分,只要规模一样 就符合 master 公式
符合子问题规模等规模的递归可以用 master 公式
- log (b,a)>d-> 复杂度为 O (N^log (b,a))
- log (b,a)=d-> 复杂度为 O (N^d * logN)
- log (b,a)<d-> 复杂度为 O (N^d)
# 归并排序
- 整体就是一个简单的递归,,左边排好序、右边排好序、让其整体有序。
- 让其整体有序的过程里用了外排序方法
- 利用 master 公式来求解时间复杂度
- 归并排序的实质:时间复杂度 (N*logⁿ), 额外空间复杂度 O (N)
/** | |
* @Author: LightRain | |
* @Description: 归并排序 时间复杂度为 O (N*logⁿ)>O (N²) | |
* 以为 O (N²) 浪费了大量的比较数据和空间,比较 N 才才会排好一个数 | |
* @DateTime: 2023-04-28 16:23 | |
* @Version:1.0 | |
**/ | |
public class MergeSort { | |
public static void main(String[] args) { | |
int[] arr = new int[]{90, -90, 62, 92, 31, 78, 59}; | |
mergeSort(arr); | |
System.out.println("Arrays.stream(arr).toArray() = " + Arrays.toString(Arrays.stream(arr).toArray())); | |
} | |
public static void mergeSort(int[] arr) { | |
if (arr == null || arr.length < 2) { | |
return; | |
} | |
process(arr, 0, arr.length - 1); | |
} | |
private static void process(int[] arr, int l, int r) { | |
// 如果一致则直接返回 | |
if (l == r) { | |
return; | |
} | |
// 找数组的中点 l+(r-l)/2 ‘>>’代表位移法,位移 1 位也是除 2 的意思 | |
int mid = l + ((r - l) >> 1); | |
// 先将左侧部分排好序 l->mid | |
process(arr, l, mid); | |
// 后将右侧部分排好序 mid+1->r | |
process(arr, mid + 1, r); | |
// | |
merge(arr, l, mid, r); | |
} | |
private static void merge(int[] arr, int l, int m, int r) { | |
// 辅助空间数组长度为 r-l+1 的长度 | |
int[] help = new int[r - l + 1]; | |
//help 数组专用下标 | |
int i = 0; | |
// 将 p1 指向 l 的下标 | |
int p1 = l; | |
// 将 p2 指向 mid+1 的下标 | |
int p2 = m + 1; | |
//p1 和 p2 下标都不越界的情况下开始进行拷贝 | |
while (p1 <= m && p2 <= r) { | |
//p1 和 p2 比较如果 p1 小于等于 p2 则先拷贝 p1 数据到 help [i] 位置然后 p1 下标 + 1,p1 不小于等于 p2 则将 p2 数据拷贝到 help [i] 位置然后 p2 下标 + 1,每进行一次 i 都会 + 1 | |
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; | |
} | |
// 如果 p1 下标没有越界就将 p1 剩下的数据一次拷贝到 help [i] 位置 | |
while (p1 <= m) { | |
help[i++] = arr[p1++]; | |
} | |
// 如果 p2 下标没有越界就将 p2 剩下的数据一次拷贝到 help [i] 位置 | |
while (p2 <= r) { | |
help[i++] = arr[p2++]; | |
} | |
for (i = 0; i < help.length; i++) { | |
// 将 help [i] 拷贝到原数组中去 | |
arr[l + i] = help[i]; | |
} | |
} | |
} |
# 归并排序的扩展 - 小和问题
- 小和问题,在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。
- 求一个数组的小和。
- 例:[1,3,4,2,5] 1 左边比 1 小的数没有,3 左边比 3 小的数有 1,4 左边比 4 小的数有 1,3,2 左边比 2 小的数有 1,5 左边比 5 小的数有 1,3,4。
- 所以小和为 1+1+3+1+1+3+4+2=16
- 逆序对问题,在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对。
/** | |
* @Author: LightRain | |
* @Description: 归并排序并求出小和 | |
* @DateTime: 2023-04-28 18:54 | |
* @Version:1.0 | |
**/ | |
public class SmallSum { | |
public static void main(String[] args) { | |
int[] arr = new int[]{1, 3, 4, 2, 5}; | |
int i = smallSum(arr); | |
System.out.println("小和 = " + i); | |
System.out.println("Arrays.stream(arr).toArray() = " + Arrays.toString(Arrays.stream(arr).toArray())); | |
} | |
private static int smallSum(int[] arr) { | |
if (arr == null || arr.length < 2) { | |
return 0; | |
} | |
return process(arr, 0, arr.length - 1); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 28/4/2023 下午 9:45 | |
* @Param: [arr, l, r] | |
* @Return: int | |
* @Description: 排序并求出小和 | |
* @since 17 | |
*/ | |
private static int process(int[] arr, int l, int r) { | |
// 如果一致则直接返回 | |
if (l == r) { | |
return 0; | |
} | |
// 找数组的中点 l+(r-l)/2 ‘>>’代表位移法,位移 1 位也是除 2 的意思 | |
int mid = l + ((r - l) >> 1); | |
// 左侧排序求小和的数量 + 右侧排序求小和的数量 + 左侧排好和右侧排好 merge 的小和的数量 | |
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r); | |
} | |
private static int merge(int[] arr, int l, int m, int r) { | |
// 辅助空间数组长度为 r-l+1 的长度 | |
int[] help = new int[r - l + 1]; | |
//help 数组专用下标 | |
int i = 0; | |
// 将 p1 指向 l 的下标 | |
int p1 = l; | |
// 将 p2 指向 mid+1 的下标 | |
int p2 = m + 1; | |
// 小和数量 | |
int res = 0; | |
//p1 和 p2 下标都不越界的情况下开始进行拷贝 | |
while (p1 <= m && p2 <= r) { | |
// 左侧比右侧小才会产生小和 (r - p2 + 1):代表当前右侧有多少个数比 p1 (p1 代表左侧) 要大 * arr [p1](左侧的数就是小和的数量) | |
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; | |
//p1 和 p2 比较如果 p1 小于 p2 则先拷贝 p1 数据到 help [i] 位置然后 p1 下标 + 1,p1 不小于等于 p2 则将 p2 数据拷贝到 help [i] 位置然后 p2 下标 + 1,每进行一次 i 都会 + 1 | |
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; | |
} | |
// 如果 p1 下标没有越界就将 p1 剩下的数据一次拷贝到 help [i] 位置 | |
while (p1 <= m) { | |
help[i++] = arr[p1++]; | |
} | |
// 如果 p2 下标没有越界就将 p2 剩下的数据一次拷贝到 help [i] 位置 | |
while (p2 <= r) { | |
help[i++] = arr[p2++]; | |
} | |
for (i = 0; i < help.length; i++) { | |
// 将 help [i] 拷贝到原数组中去 | |
arr[l + i] = help[i]; | |
} | |
return res; | |
} | |
} |
import java.util.Arrays; | |
/** | |
* @Author: LightRain | |
* @Description: 逆序对 | |
* @DateTime: 2023-04-28 18:54 | |
* @Version:1.0 | |
**/ | |
public class ReverseSequencePair { | |
public static void main(String[] args) { | |
int[] arr = new int[]{3, 2, 4, 5, 0}; | |
int i = smallSum(arr); | |
System.out.println("逆序对数量 = " + i); | |
System.out.println("Arrays.stream(arr).toArray() = " + Arrays.toString(Arrays.stream(arr).toArray())); | |
} | |
private static int smallSum(int[] arr) { | |
if (arr == null || arr.length < 2) { | |
return 0; | |
} | |
return process(arr, 0, arr.length - 1); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 28/4/2023 下午 9:45 | |
* @Param: [arr, l, r] | |
* @Return: int | |
* @Description: 排序并求出逆序对数量 | |
* @since 17 | |
*/ | |
private static int process(int[] arr, int l, int r) { | |
// 如果一致则直接返回 | |
if (l == r) { | |
return 0; | |
} | |
// 找数组的中点 l+(r-l)/2 ‘>>’代表位移法,位移 1 位也是除 2 的意思 | |
int mid = l + ((r - l) >> 1); | |
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r); | |
} | |
private static int merge(int[] arr, int l, int m, int r) { | |
// 辅助空间数组长度为 r-l+1 的长度 | |
int[] help = new int[r - l + 1]; | |
//help 数组专用下标 | |
int i = 0; | |
// 将 p1 指向 l 的下标 | |
int p1 = l; | |
// 将 p2 指向 mid+1 的下标 | |
int p2 = m + 1; | |
// 逆序对数量 | |
int res = 0; | |
//p1 和 p2 下标都不越界的情况下开始进行拷贝 | |
while (p1 <= m && p2 <= r) { | |
if (arr[p1] > arr[p2]) { | |
res++; | |
// 打印逆序对 | |
System.out.println(arr[p1] + " " + arr[p2]); | |
} | |
//p1 和 p2 比较如果 p1 大于 p2 则先拷贝 p1 数据到 help [i] 位置然后 p1 下标 + 1,p1 不小于等于 p2 则将 p2 数据拷贝到 help [i] 位置然后 p2 下标 + 1,每进行一次 i 都会 + 1 | |
help[i++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++]; | |
} | |
// 如果 p1 下标没有越界就将 p1 剩下的数据一次拷贝到 help [i] 位置 | |
while (p1 <= m) { | |
help[i++] = arr[p1++]; | |
} | |
// 如果 p2 下标没有越界就将 p2 剩下的数据一次拷贝到 help [i] 位置 | |
while (p2 <= r) { | |
help[i++] = arr[p2++]; | |
} | |
for (i = 0; i < help.length; i++) { | |
// 将 help [i] 拷贝到原数组中去 | |
arr[l + i] = help[i]; | |
} | |
return res; | |
} | |
} |
# 荷兰国旗问题
# 问题一
- 给定一个数组 arr,和一个数 num,请把小于等于 num 的数放在数组的左边。
- 大于 num 的数放在数组的右边,要求额外空间复杂度为 O (1),时间复杂度为 O (N)。
# 问题二 (荷兰国旗问题)
- 给定一个数组 arr,和一个数 num,请把小于 num 的数放在数组的左边,等于 num 的数放在数组的中间。
- 大于 num 的数放在数组的右边,要求额外复杂度为 O (1),时间复杂度为 O (N)。
import java.util.Arrays; | |
/** | |
* @Author: LightRain | |
* @Description: 快速排序 O (N*logⁿ) | |
* @DateTime: 2023-05-05 17:09 | |
* @Version:1.0 | |
**/ | |
public class QuickSort { | |
public static void main(String[] args) { | |
long startTime = System.currentTimeMillis(); | |
int[] arr= new int[]{90, 61,92,31,78,59}; | |
quickSort(arr); | |
System.out.println("Arrays.stream(arr).toArray() = " + Arrays.toString(Arrays.stream(arr).toArray())); | |
long endTime = System.currentTimeMillis(); | |
System.out.println("程序运行时间: " + (endTime - startTime ) + "ms"); | |
} | |
private static void quickSort(int[] arr) { | |
if (arr == null || arr.length < 2) { | |
return; | |
} | |
partition(arr, 0, arr.length - 1); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 5/5/2023 下午 6:28 | |
* @Param: [arr, l, r] | |
* @Return: int [] | |
* @Description: 默认以 arr [r] 做划分,arr [r] --> p <p ==p >p | |
* 返回等于区域 (左边界,又边界),所以返回一个长度为 2 的数组 res,res [0] res [1] | |
* @since 17 | |
*/ | |
private static int[] partition(int[] arr, int l, int r) { | |
// 小于区边界 | |
int less = l - 1; | |
// 大于区边界 | |
int more = r; | |
//l 表示当前数的位置 arr [r] --> 划分值 | |
while (l < more) { | |
// 当前数小于划分值 | |
if (arr[l] < arr[r]) { | |
swap(arr, ++less, l++); | |
// 当前数大于划分值 | |
} else if (arr[l] > arr[r]) { | |
swap(arr, --more, l); | |
} else { | |
l++; | |
} | |
} | |
swap(arr, more, r); | |
return new int[]{less + 1, more}; | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 5/5/2023 下午 6:38 | |
* @Param: [arr, i, j] | |
* @Return: void | |
* @Description: 交换 | |
* @since 17 | |
*/ | |
private static void swap(int[] arr, int i, int j) { | |
int tmp = arr[i]; | |
arr[i] = arr[j]; | |
arr[j] = tmp; | |
} | |
} |
# 快速排序
import java.util.Arrays; | |
/** | |
* @Author: LightRain | |
* @Description: 快速排序 O (N*logⁿ) | |
* @DateTime: 2023-05-05 17:09 | |
* @Version:1.0 | |
**/ | |
public class QuickSort { | |
public static void main(String[] args) { | |
long startTime = System.currentTimeMillis(); | |
int[] arr= new int[]{90, 61,92,31,78,59}; | |
quickSort(arr); | |
System.out.println("Arrays.stream(arr).toArray() = " + Arrays.toString(Arrays.stream(arr).toArray())); | |
long endTime = System.currentTimeMillis(); | |
System.out.println("程序运行时间: " + (endTime - startTime ) + "ms"); | |
} | |
private static void quickSort(int[] arr) { | |
if (arr == null || arr.length < 2) { | |
return; | |
} | |
quickSort(arr, 0, arr.length - 1); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 5/5/2023 下午 6:28 | |
* @Param: [arr, l, r] | |
* @Return: void | |
* @Description: arr [l.....r] 进行排序 | |
* @since 17 | |
*/ | |
private static void quickSort(int[] arr, int l, int r) { | |
if (l < r) { | |
swap(arr, l + (int) (Math.random() * (r - l + 1)), r); | |
int[] p = partition(arr, l, r); | |
// 小于区 p [0]: 等于区域的左边界然后减 1 为小于区右边界 | |
quickSort(arr, l, p[0] - 1); | |
// 大于区 p [1]: 等于区域的右边界然后加 1 为大于区左边界 | |
quickSort(arr, p[1] + 1, r); | |
} | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 5/5/2023 下午 6:28 | |
* @Param: [arr, l, r] | |
* @Return: int [] | |
* @Description: 默认以 arr [r] 做划分,arr [r] --> p <p ==p >p | |
* 返回等于区域 (左边界,又边界),所以返回一个长度为 2 的数组 res,res [0] res [1] | |
* @since 17 | |
*/ | |
private static int[] partition(int[] arr, int l, int r) { | |
// 小于区边界 | |
int less = l - 1; | |
// 大于区边界 | |
int more = r; | |
//l 表示当前数的位置 arr [r] --> 划分值 | |
while (l < more) { | |
// 当前数小于划分值 | |
if (arr[l] < arr[r]) { | |
swap(arr, ++less, l++); | |
// 当前数大于划分值 | |
} else if (arr[l] > arr[r]) { | |
swap(arr, --more, l); | |
} else { | |
l++; | |
} | |
} | |
swap(arr, more, r); | |
return new int[]{less + 1, more}; | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 5/5/2023 下午 6:38 | |
* @Param: [arr, i, j] | |
* @Return: void | |
* @Description: 交换 | |
* @since 17 | |
*/ | |
private static void swap(int[] arr, int i, int j) { | |
int tmp = arr[i]; | |
arr[i] = arr[j]; | |
arr[j] = tmp; | |
} | |
} |