归并排序:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
-
把长度为n的输入序列分成两个长度为n/2的子序列;
-
对这两个子序列分别采用归并排序;
-
将两个排序好的子序列合并成一个最终的排序序列。
递归方法:
package com.zy.base.class003;
public class Code01_MergeSort {
public static void main(String[] args) {
int[] arr = {10, 2, 5, 6, 1, 3, 9};
new Code01_MergeSort().mergeSort(arr);
System.out.println();
}
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
procss(arr, 0, arr.length - 1);
}
private static void procss(int[] arr, int start, int end) {
if (start == end) {
return;
}
int mid = start + (end - start) / 2;
procss(arr, start, mid);
procss(arr, mid + 1, end);
merge(arr, start, mid, end);
}
private static void merge(int[] arr, int start, int mid, int end) {
int[] helper = new int[end - start + 1];
int left = start;
int right = mid + 1;
int helperIndex = 0;
while (left <= mid && right <= end) {
helper[helperIndex++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
}
while (left <= mid) {
helper[helperIndex++] = arr[left++];
}
while (right <= end) {
helper[helperIndex++] = arr[right++];
}
for (int i = 0; i < helper.length; i++) {
arr[start + i] = helper[i];
}
}
}
非递归方法:
package com.zy.base.class003;
public class Code01_MergeSort1 {
public static void main(String[] args) {
int[] arr = {10, 2, 5, 6, 1, 3, 9};
new Code01_MergeSort1().mergeSort(arr);
System.out.println();
}
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
procss(arr, 0, arr.length - 1);
}
private static void procss(int[] arr, int start, int end) {
if (start == end) {
return;
}
int len = arr.length;
int mergeSize = 1;
while (mergeSize < len) {
int left = 0;
while (left < len) {
int m = left + mergeSize - 1;
if (m >= len) {
break;
}
int right = Math.min(m + mergeSize, len - 1);
merge(arr, left, m, right);
left = right + 1;
}
if (mergeSize > len / 2) {
break;
}
mergeSize <<= 1;
}
}
private static void merge(int[] arr, int start, int mid, int end) {
int[] helper = new int[end - start + 1];
int left = start;
int right = mid + 1;
int helperIndex = 0;
while (left <= mid && right <= end) {
helper[helperIndex++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
}
while (left <= mid) {
helper[helperIndex++] = arr[left++];
}
while (right <= end) {
helper[helperIndex++] = arr[right++];
}
for (int i = 0; i < helper.length; i++) {
arr[start + i] = helper[i];
}
}
}
常见面试题:
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例如:[1, 3, 4, 2, 5]
1左边比1小的数:
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、2
所以数组的小和为1+1+3+1+1+3+4+2=16
上代码:
package com.zy.base.class003;
public class Code02_SmallSum {
public static void main(String[] args) {
int[] arr = {1, 3, 4, 2, 5};
int i = Code02_SmallSum.smallSum(arr);
System.out.println(i);
}
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
private static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + (right - left) / 2;
return process(arr, left, mid) + process(arr, mid + 1, right) + merge(arr, left, mid, right);
}
private static int merge(int[] arr, int start, int mid, int end) {
int[] helper = new int[end - start + 1];
int res = 0;
int left = start;
int right = mid + 1;
int helperIndex = 0;
while (left <= mid && right <= end) {
res += arr[left] < arr[right] ? (end - right + 1) * arr[left] : 0;
helper[helperIndex++] = arr[left] < arr[right] ? arr[left++] : arr[right++];
}
while (left <= mid) {
helper[helperIndex++] = arr[left++];
}
while (right <= end) {
helper[helperIndex++] = arr[right++];
}
for (int i = 0; i < helper.length; i++) {
arr[start + i] = helper[i];
}
return res;
}
}
求逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
package com.zy.offer;
/**
* 剑指 Offer 51. 数组中的逆序对
* 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
*
*
*
* 示例 1:
*
* 输入: [7,5,6,4]
* 输出: 5
*
*
* 限制:
*
* 0 <= 数组长度 <= 50000
*/
public class Offer51 {
public int reversePairs(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
return process(nums, 0, nums.length - 1);
}
private int process(int[] nums, int start, int end) {
if (start == end) {
return 0;
}
int mid = start + (end - start) / 2;
return process(nums, start, mid) + process(nums, mid + 1, end) + merge(nums, start, mid, end);
}
private int merge(int[] nums, int start, int mid, int end) {
int[] help = new int[end - start + 1];
int left = start;
int right = mid + 1;
int index = 0;
int res = 0;
while (left <= mid && right <= end) {
res += nums[left] > nums[right] ? (mid - left + 1) : 0;
help[index++] = nums[left] > nums[right] ? nums[right++] : nums[left++];
}
while (left <= mid) {
help[index++] = nums[left++];
}
while (right <= end) {
help[index++] = nums[right++];
}
for (int i = 0; i < help.length; i++) {
nums[start + i] = help[i];
}
return res;
}
}