剖析递归行为和递归行为时间复杂度
归并排序
1)就是一个简单的递归,左边排好序,右边排好序,让其整体有序。
2)分治算法,先分,再合
已知数组[3,4,5,7,9.6,2,1]
首先将数组拆分,得到多干个长度为2的数组,首先比较左边部分第一个分裂点。
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,3比4小 ,拷贝3,继续走发现3再走就超过mid了要越界了,跳出循环。
把4那一半全部拷贝下来。3 4 依然是3 4
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,5比7小 ,拷贝5,继续走发现5再走就超过mid了要越界了,跳出循环。
把4那一半全部拷贝下来。5 7依然是5 7
分裂点结果合并。
将数组按照mid切割,一个从左端L出发一个从mid+ 1出发,比较谁小就复制谁,3比5小 ,拷贝3,L + 1,4和5比,4小,拷贝4,然后发现L继续向右走就超过mid了要越界了,跳出循环。
把mid + 1 到R那一半全部拷贝下来。3 4 5 7依然是3 4 5 7
比较右边部分第一个分裂点。
接下来就进行比较了
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,6比9小 ,拷贝6,继续走发现6越界了,跳出循环。
直到一个过界了,把9那一半全部拷贝下来。9 6 变成6 9
比较右边部分第二个分裂点。
接下来就进行比较了
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,1比2小 ,拷贝1,继续走发现1越界了,跳出循环。
直到一个过界了,把2那一半全部拷贝下来。2 1 变成 1 2
合并结果
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,1比6小 ,拷贝1,L继续向右走发现2 比9小拷贝2,接着L打算向右走发现超过mid了越界了,跳出循环。
把6 9 那一半全部拷贝下来。12 6 9 仍为1 2 6 9
合并结果
将数组按照mid切割,一个从左端0出发一个从mid+ 1出发,比较谁小就复制谁,1比3小 ,拷贝1,mid + 1 继续向右走发现2 比3小拷贝2,接着mid + 1 发现3 比 6 小向L 右走,拷贝3,L向右走发现4 比6小,拷贝4,L向右走,5比6小,拷贝5,L向右走7比6大,拷贝6,6向右走发现7比9小,拷贝7,L向右走,发现再有 就超过mid了越界了,跳出循环。
排好序了。
private static void merge(int[] arr, int L, int R) {
if (L == R) {
return;
}
int mid = L + (R - L) / 2;
merge(arr, L, mid);
merge(arr, mid + 1, R);
mergeSort(arr, L, R);
}
private static void mergeSort(int[] arr, int L, int R) {
int mid = L + (R - L) / 2;
int k = mid + 1;
int i = L;
int[] temp = new int[R - L + 1];
int size = 0;
while (k <= R && i <= mid) {
if (arr[k] < arr[i]) {
temp[size++] = arr[k++];
} else {
temp[size++] = arr[i++];
}
}
while (k <= R) {
temp[size++] = arr[k++];
}
while (i <= mid) {
temp[size++] = arr[i++];
}
System.arraycopy(temp, 0, arr, L, temp.length);
}
归并排序的思想延申问题
- 小和问题,在一个数组中,每一个数左边比当前数小的数添加起来,叫做这个数组的小和,求一个数组的小和。例子:[1,3,4, 2,5]左边比1小的数没有,3左边比3小的数1,4 左边比4小的数1,3,2,2 左边比2小的数1,5左边比1的数,1、3、4,所以小和1 +1 + 3 + 1 + 1+ 3 + 4 + 2 =16。左数比当前数小的数累加起来,可以理解为右数有几个比我大的与我相乘的和
private static int minorSum(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + (R - L) / 2;
return minorSum(arr, L, mid) +
minorSum(arr, mid + 1, R) + sumLargerNum(arr,L, R);
}
private static int sumLargerNum(int[] arr, int L, int R) {
int mid = L + (R - L) / 2;
int p = L;
int p1 = mid + 1;
int i = 0;
int ret = 0;
int[] temp = new int[R - L + 1];
while (p <= mid && p1 <= R) {
//小和运算,左边有多少个数比我小的累加之和等价于是右边有多少个比当前数大的数*当前数
//如果当前p2比我大,p2往后直到R都比p大
if (arr[p1] > arr[p]) {
ret += arr[p] * (R - p1 + 1);
temp[i++] = arr[p++];
} else {
temp[i++] = arr[p1++];
}
}
while (p <= mid) {
temp[i++] = arr[p++];
}
while (p1 <= R) {
temp[i++] = arr[p1++];
}
System.arraycopy(temp, 0, arr, L, temp.length);
return ret;
}
逆序对问题
什么是逆序对?
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 且 A[i] > A[j], 则 <A[i], A[j]> 这个有序对, 称为A的一个逆序对, 也称作逆序数。
数组a:2,3,8,6,1
从左边第一个数开始向右逐个数遍历,第一和第五个数可以组成一个逆序对<2,1>,即1<5,a[1]>a[5]。数组a共有5个逆序对,分别是:<2,1> < 3, 1> <8, 6> <8, 1> <6, 1>
在一个数组中,左边的数比右边的数大,则这两个数构成逆序对,请打印所有逆序对。
private static int countReverse(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + (R - L) / 2;
return countReverse(arr, L, mid) +
countReverse(arr, mid + 1, R) + reverse(arr,L, R);
}
private static int reverse(int[] arr, int L, int R) {
int mid = (R - L) / 2 + L;
int p = L;
int p1 = mid + 1;
int[] temp = new int[R - L + 1];
int count = 0;
int i = 0;
while (p <= mid && p1 <= R) {
//左边的数比右边的数大,则左半拉到mid都比右面这个p1值要大
if (arr[p] > arr[p1]) {
count += (mid - p + 1);
temp[i++] = arr[p1++];
} else {
temp[i++] = arr[p++];
}
}
while (p <= mid) {
temp[i++] = arr[p++];
}
while (p1 <= R) {
temp[i++] = arr[p1++];
}
System.arraycopy(temp, 0, arr, L, temp.length);
return count;
}