-
归并排序
一、递归实现
- 取数组的中间值
- 分别对左右两遍的数组进行相同处理
- 最后进行合并(重点)
- 使用辅助数组来记录排序后的结果
- 将数组分为了左组和右组
- 通过三个指针来进行标记(i,p1,p2),分别指向的是辅助数组,左组和右组
- 在p1和p2都不越界的情况下,通过比较p1和p2的数值,如果p1<p2那么就p1对应值给辅助数组,且指向下一个数(p2同理)
- 最终要么p1越界,要么p2越界,把还没越界的那组后续的数全部赋值到辅助数组中
- 最后将辅助数组的值填充到原始数组中
public static void mergeSort1(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;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr, L, mid, R);
}
private static void merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
二、非递归实现
- 考虑步长问题
- 考虑左组和右组的边界问题
- 定义M变量表示左组的末尾,通过它和总长比较来判断左组是否够长,不够则直接跳出循环
- 如果左组够长,则考虑右组的边界,右组够则为左组末尾 + 步长,不够则为数组末尾,所以取两者的最小值即可
- 合并并将左组初始值初始化为右组末尾+1
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
int mergeSize = 1;
while (mergeSize < N) {
int L = 0;
while (L < N) {
int M = L + mergeSize - 1;
if (M >= N) {
break;
}
int R = Math.min(M + mergeSize, N - 1);
merge(arr, L, M, R);
L = R + 1;
}
if (mergeSize > N / 2) {
break;
}
mergeSize <<= 1;
}
}
public static int[] generateArr(int maxSize, int maxvalue) {
int[] arr = new int[(int) (Math.random() * (maxSize + 1))];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * (maxvalue + 1)) - (int) (Math.random() * (maxvalue + 1));
}
return arr;
}
public static int[] copyArr(int[] arr) {
int[] newArr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
return newArr;
}
public static boolean isEqual(int[] arr1, int[] arr2) {
if (arr1 == null ^ arr2 == null) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
for (int j = 0; j < arr2.length; j++) {
if (arr1[i] != arr2[j]) {
return false;
}
}
}
return true;
}
public static void main(String[] args) {
int testTimes = 10000;
int maxSize = 100;
int maxValue = 100;
System.out.println("begin");
for (int i = 0; i < testTimes; i++) {
int[] arr1 = generateArr(maxSize, maxValue);
mergeSort1(arr1);
int[] arr2 = copyArr(arr1);
mergeSort2(arr2);
if (!isEqual(arr1, arr2)) {
System.out.println("fail");
break;
}
}
}
-
将数组排好序并返回小和(小和:当前数左边比它小的数的和)
- 取数组的中间值
- 分别对左右两遍的数组进行相同处理和合并
- 返回左右两边的处理值和合并的值的和
- 合并(重点)
- 求当前数之前比它小的数 -> 反向角度思考 ->就是求左组数比右组数小的个数
- 因为是在有序的基础上,所以只要左组数比右组第一个数小,那么就比它后面的数都小
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + ((R - L) >> 1);
return
process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
}
public static int merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
int res = 0;
while (p1 <= M && p2 <= R) {
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
public static int test(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
-
将数组排好序并返回逆序对个数(逆序对:左边比右边数小的组合)
- 其实就是在归并排序基础上进行一些操作
- 假设我们是以左组为角度的话,当比较的时候两组对应的值相等时,应先将右组的复制给辅助数组
public static int reversePairNumber(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return process(arr, l, mid) + process(arr, mid + 1, r) + merge1(arr, l, mid, r);
}
private static int merge1(int[] arr, int l, int mid, int r) {
int[] help = new int[r - l + 1];
int i = help.length - 1;
int p1 = mid;
int p2 = r;
int res = 0;
while (p1 >= l && p2 > mid) {
res += arr[p1] > arr[p2] ? (p2 - mid) : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= l) {
help[i--] = arr[p1--];
}
while (p2 > mid) {
help[i--] = arr[p2--];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return res;
}
private static int merge2(int[] arr, int l, int mid, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = mid + 1;
int ans = 0;
while (p1 <= mid && p2 <= r) {
ans += arr[p1] > arr[p2] ? (p2-mid) : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return ans;
}
public static int test(int[] arr) {
int ans = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
ans++;
}
}
}
return ans;
}
public static void main(String[] args) {
int testTimes = 10000;
int maxSize = 8;
int maxValue = 20;
System.out.println("begin");
for (int i = 0; i < testTimes; i++) {
int[] arr1 = generateArr(maxSize, maxValue);
int[] arr2 = copyArr(arr1);
int ans1 = reversePairNumber(arr1);
int ans2 = test(arr2);
if (ans1 != ans2) {
System.out.println("ans1:"+ans1);
System.out.println("ans2:"+ans2);
System.out.println("fail");
break;
}
}
System.out.println("end");
}
-
将数组排好序并返回左数比有数两倍大的数的总个数
- 依旧是在归并排序的基础上来进行实现
- 定义了windowR指针,这个指针采用的是左闭右开[M+1,windowR)的原则,得到右组中满足条件的最终位置,从而算出个数
public static int biggerTwice(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
private static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
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 mid, int r) {
int ans = 0;
int windowR = mid + 1;
for (int i = l; i <= mid; i++) {
while (windowR <= r && arr[i] > (arr[windowR] << 1)) {
windowR++;
}
ans += windowR - mid - 1;
}
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = mid + 1;
while (p1 <= mid && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return ans;
}
public static int test(int[] arr) {
int ans = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > (arr[j] << 1)) {
ans++;
}
}
}
return ans;
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateArr(maxSize, maxValue);
int[] arr2 = copyArr(arr1);
if (biggerTwice(arr1) != test(arr2)) {
System.out.println("Oops!");
break;
}
}
System.out.println("测试结束");
}