一、归并排序
- 问题背景:杠铃增重问题,每位参赛运动员向组委会提交排好序得三次举重量,为了便于杠铃得拆卸,组委会需对所有试举重量递增排序。
- 已学过的解决方案
- 选择排序:从待排序元素中迭代选出最小值并排序
- 插入排序:依次将每个元素插入到已排序序列中
- 分析杠铃增重问题
- 特点:局部有序
- 快速合并:比较两个有序数组当前最小元素,将较小者逐一合入新数组
- 后续策略:
- 杠铃增重问题代码实现
package com.tiger.study;
import java.util.ArrayList;
import java.util.List;
public class MergeSort {
public static void main(String[] args) {
// 杠铃增重问题
int[] l1 = {4, 10, 19};
int[] l2 = {13, 16, 18};
int[] l3 = {5, 9, 12};
int[] l4 = {11, 15, 17};
int[] mergeList = merge(merge(l1, l2), merge(l3, l4));
for (int i = 0; i < mergeList.length; i++) {
System.out.println(mergeList[i]);
}
}
private static int[] merge(int[] a, int[] b) {
int[] tempList = new int[a.length + b.length];
int a_head = 0, b_head = 0;
int count = 0;
while (a_head < a.length && b_head < b.length) {
if (a[a_head] <= b[b_head]) {
tempList[count] = a[a_head++];
count++;
} else {
tempList[count] = b[b_head++];
count++;
}
}
if (a_head < a.length) {
for (int i = a_head; i < a.length; i++) {
tempList[count] = a[i];
count++;
}
} else if (b_head < a.length) {
for (int i = b_head; i < b.length; i++) {
tempList[count] = b[i];
count++;
}
}
return tempList;
}
}
- 从杠铃增重问题转换为排序问题
- 相较于杠铃增重问题而言,排序问题的问题输入有所改变,排序问题中以完整的数组输入,同时局部有序缺失。
- 针对于问题输入的变化,我们可以将问题分解,当输入长度为1时,数组天然有序,这项可以处理局部有序确实的变化。
- 归并排序算法流程:
- 将数组排序问题分解为和的排序问题
- 递归解决子问题得到两个有序的子序列
- 将两个有序的子数组合并为一个有序数组
package com.tiger.study;
public class MergeSort {
public static void main(String[] args) {
// 归并排序
int[] arr = {24, 17, 40, 28, 13, 14, 22, 32, 40, 21, 48, 4, 47, 8, 37, 18};
int[] result = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, result);
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
}
// 递归方法
private static void mergeSort(int[] arr, int left, int right, int[] result) {
// 递归结束条件
if (right == left) return;
int mid = (right + left) / 2;
mergeSort(arr, left, mid, result);
mergeSort(arr, mid + 1, right, result);
merge(arr, left, right, result);
}
// 合并有序的部分
private static void merge(int[] arr, int left, int right, int[] result) {
int mid = (right + left) / 2;
int left_head = left, right_head = mid + 1, result_index = left;
while (left_head <= mid && right_head <= right) {
if (arr[left_head] <= arr[right_head]) {
result[result_index++] = arr[left_head++];
} else {
result[result_index++] = arr[right_head++];
}
}
if (left_head <= mid) {
for (int i = left_head; i <= mid; i++) {
result[result_index++] = arr[i];
}
}
if (right_head <= right) {
for (int i = right_head; i <= right; i++) {
result[result_index++] = arr[i];
}
}
for (int i = left; i <= right; i++) {
arr[i] = result[i];
}
}
}
二、递归式求解
- 代入法:这种方法比较看直觉,要先猜一个,然后去证明
- 主定理法:这个方法有一堆的推导,然后我们作题的话只要记住下面的形式就行了
三、最大子数组问题
- 问题描述:数组X中有若干个子数组,每个子数组为X中的一段序列,寻找X中最大的非空子数组。
package com.tiger.study;
// 最大子数组问题:数组X中有若干个子数组,每个子数组为X中的一段序列,寻找X中最大的非空子数组
import java.util.ArrayList;
import java.util.Arrays;
public class MaxSubArray {
public static void main(String[] args) {
int[] arr = {1, -2, 4, 5, -2, 8, 3, -2, 6, 3, 7, -1};
// 暴力解法
ArrayList<Integer> maxSubArray = new ArrayList<>();
int maxValue = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
int temp = 0;
for (int j = i; j < arr.length; j++) {
temp += arr[j];
if (temp >= maxValue) {
maxValue = temp;
maxSubArray.clear();
for (int k = i; k <= j; k++) {
maxSubArray.add(arr[k]);
}
}
}
}
for (int i = 0; i < maxSubArray.size(); i++) {
System.out.println(maxSubArray.get(i));
}
// 分治策略
int[] maxSubArray1 = maxSubArray(arr, 0, arr.length - 1);
for (int i = 0; i < maxSubArray1.length; i++) {
System.out.println(maxSubArray1[i]);
}
}
private static int[] maxSubArray(int[] arr, int low, int high) {
if (low == high) {
return new int[]{arr[low]};
} else {
int mid = (low + high) / 2;
int[] s1 = maxSubArray(arr, low, mid);
int[] s2 = maxSubArray(arr, mid + 1, high);
int[] s3 = crossingSubArray(arr, low, mid, high);
int[] sMax = max(s1, s2, s3);
return sMax;
}
}
private static int[] crossingSubArray(int[] arr, int low, int mid, int high) {
int left_head = mid, right_head = mid + 1;
int temp = 0;
int max = Integer.MIN_VALUE;
for (int i = mid; i >= low; i--) {
temp += arr[i];
if (temp > max) {
left_head = i;
max = temp;
}
}
temp = 0;
max = Integer.MIN_VALUE;
for (int i = mid + 1; i <= high; i++) {
temp += arr[i];
if (temp > max) {
right_head = i;
max = temp;
}
}
int[] result = new int[right_head - left_head + 1];
for (int i = left_head, j = 0; i <= right_head && j < result.length; i++, j++) {
result[j] = arr[i];
}
return result;
}
private static int[] max(int[] s1, int[] s2, int[] s3) {
int sum1 = Arrays.stream(s1).sum();
int sum2 = Arrays.stream(s2).sum();
int sum3 = Arrays.stream(s3).sum();
int max = Math.max(Math.max(sum1, sum2), sum3);
if (max == sum1) {
return s1;
} else if (max == sum2) {
return s2;
} else {
return s3;
}
}
}
四、逆序对计数问题
- 问题描述:数组中存在下标小的值比下标大的值大的称为逆序对,求数组中一共有多少个逆序对?
package com.tiger.study;
// 逆序对计数问题:数组中共有多少个逆序对
public class CountingInversions {
public static void main(String[] args) {
int[] arr = {13, 8, 10, 6, 15, 18, 12, 20, 9, 14, 17, 19};
System.out.println(countingInversions(arr));
System.out.println(countInver(arr, 0, 2));
}
// 暴力方法
private static int countingInversions(int[] arr) {
int count = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
count++;
}
}
}
return count;
}
// 分治策略
private static int countInver(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = (left + right) / 2;
int s1 = countInver(arr, left, mid);
int s2 = countInver(arr, mid + 1, right);
int s3 = mergeCount(arr, left, mid, right);
int s = s1 + s2 + s3;
return s;
}
private static int mergeCount(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int tempIndex = 0;
int left_head = left, right_head = mid + 1;
int s3 = 0;
while (right_head <= right && left_head <= mid) {
if (arr[left_head] > arr[right_head]) {
temp[tempIndex++] = arr[right_head++];
} else {
temp[tempIndex++] = arr[left_head++];
s3 += right_head - mid - 1;
}
}
if (left_head <= mid) {
for (int i = left_head; i <= mid; i++) {
temp[tempIndex++] = arr[i];
s3 += (right - mid);
}
}
if (right_head <= right) {
for (int i = right_head; i <= right; i++) {
temp[tempIndex++] = arr[i];
}
}
for (int i = 0; i < temp.length; i++) {
arr[left + i] = temp[i];
}
return s3;
}
}