三、归并排序及其应用
1、归并排序递归实现
- 假设桌子上有两沓顺序排好的手牌,每次取两边较小的一个,这样最后的手牌总体就是有序的。归并排序就是这样的思想,假设数组分为左右两部分别有序,新建立一个数组,每次取较小的数放入新数组中,这样最终的数组就是有序的。
package merge;
public class MergeSort {
public static void sort(int[] arr){
if(arr == null || arr.length == 0){
return;
}
mergeSort(arr,0,arr.length - 1);
}
private static void mergeSort(int[] arr , int L, int R){
if(L == R){
return;
}
int mid = L + ((R - L) >> 1);
mergeSort(arr,L,mid);
mergeSort(arr,mid + 1,R);
merge(arr,L,R,mid + 1);
}
private static void merge(int[] arr,int L,int R,int mid){
int[] newArr = new int[R - L + 1];
int leftBegin = L;
int rightBegin = mid;
int k = 0;
while (leftBegin < mid && rightBegin <= R){
newArr[k++] = arr[leftBegin] <= arr[rightBegin] ? arr[leftBegin++] : arr[rightBegin++];
}
while (leftBegin < mid){
newArr[k++] = arr[leftBegin++];
}
while ((rightBegin <= R)){
newArr[k++] = arr[rightBegin++];
}
for(int i = 0;i < newArr.length;i++){
arr[L + i] = newArr[i];
}
}
}
2、求小和
- 给定一个整型数组,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和
- 例子 arr = [2,4,1,6]
- 对于2,左边比他小的没有
- 对于4,左边2比他小
- 对于3,左边没有比他小的
- 对于6,左边2、4、1都比它小
- 所有返回 2 + 2 + 4 + 1 = 9
2.1 两层for循环,O(n2)时间复杂度
- 很显然两层for循环可以搞定这个问题。外循环依次遍历数组,内循环从数组起始位置到当前位置,所有比它小的加起来就行。
public static int smallSumFor(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
int sum = 0;
for(int i = 1;i < arr.length;i++){
for(int j = 0;j <= i;j++){
if(arr[j] < arr[i]){
sum += arr[j];
}
}
}
return sum;
}
2.2 通过归并求小和O(N*log2N)
- 每次在归并的过程中,只要左边当前位置的元素的比右边前位置的元素小,那么左边的这个数就会对右边部分 当前位置向右的所有数产生小和,那么记录右边数组到右边当前的位置之差乘上左边部分当前的值,就是在这次归并中左边该数产生的小和。因为每次都是局部归并,小和每次归并只对右边部分产生,但是归并会两两合并,小归并到大归并,那么在往顶部的归并中产生的所有小和都会被统计起来。因为每次只比较左右两部分,所有在前一次归并中右边部分的小和不会再被记录,直接看图可能比较好理解。
public static int smallSumMergeSort(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
return mergeSort(arr,0,arr.length - 1);
}
public static int mergeSort(int[] arr, int L, int R){
if(L == R){
return 0;
}
int mid = L + ( (R - L) >> 1);
return mergeSort(arr,L,mid) +
mergeSort(arr,mid + 1,R) +
merge(arr,L,mid + 1,R);
}
public static int merge(int[] arr,int L,int mid,int R){
int[] newArr = new int[R - L + 1];
int leftBegin = L;
int rightBegin = mid;
int k = 0;
int result = 0;
while (leftBegin < mid && rightBegin <= R){
result += arr[leftBegin] < arr[rightBegin] ? (R - rightBegin + 1) * arr[leftBegin] : 0;
newArr[k++] = arr[leftBegin] < arr[rightBegin] ? arr[leftBegin++] : arr[rightBegin++];
}
while (leftBegin < mid){
newArr[k++] = arr[leftBegin++];
}
while ((rightBegin <= R)){
newArr[k++] = arr[rightBegin++];
}
for(int i = 0;i < newArr.length;i++){
arr[L + i] = newArr[i];
}
return result;
}
3、逆序对
- 对于数组中的元素,i和j为下标,若i < j 时,arr[i] > arr[j] 就称这是一个逆序对。
- 给定一个数组,返回数组中多少个逆序对
- 两层for循环,不在赘述。
- 同上题一样,可以用归并排序。在归并排序中每次统计逆序对,最后结果加起来就是结果
- 如果左边的当前位置和右边当前位置为逆序对,那么左边当前位置往后的所有位置都与右边当前位置是逆序对,因为数组两部分都是有序的。
- 这里不在详细解释,思路都差不多,只不过统计方式需要改变,这种局部到全局的统计思路都大体相同。
package merge;
public class Reverse {
public static int reverse(int[] arr){
if(arr == null || arr.length == 0){
return 0;
}
return mergeSort(arr,0,arr.length - 1);
}
public static int mergeSort(int[] arr,int L,int R){
if(L == R){
return 0;
}
int mid = L + ( (R - L) >> 1);
return mergeSort(arr,L,mid) +
mergeSort(arr,mid + 1,R) +
merge(arr,L,mid + 1,R);
}
private static int merge(int[] arr,int L,int mid,int R){
int[] newArr = new int[R - L + 1];
int leftBegin = L;
int rightBegin = mid;
int k = 0;
int result = 0;
while (leftBegin < mid && rightBegin <= R){
result += arr[leftBegin] > arr[rightBegin] ? (mid - leftBegin) : 0;
newArr[k++] = arr[leftBegin] <= arr[rightBegin] ? arr[leftBegin++] : arr[rightBegin++];
}
while (leftBegin < mid){
newArr[k++] = arr[leftBegin++];
}
while ((rightBegin <= R)){
newArr[k++] = arr[rightBegin++];
}
for(int i = 0;i < newArr.length;i++){
arr[L + i] = newArr[i];
}
return result;
}
}
4、将逆序对的定义改为i和j为下标,若i < j 时,arr[i] > 2 *arr[j] 就称这是一个逆序对,寻找所有逆序对
- 原理同上一个题目差不多,只需要修改判断条件
- 但是这里的结果统计和归并不可以一起进行,因为这种判断和归并条件不同,所以可以先统计结果再进行归并
- 因此可以扩展,对于不同的要求,都可以先统计结果再进行归并
package merge;
public class BigThanRightTwice {
public static int bigThanRightTwice(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int mid = L + ((R - L) >> 1);
return mergeSort(arr, L, mid) +
mergeSort(arr, mid + 1, R) +
merge(arr, L, mid + 1, R);
}
private static int merge(int[] arr, int L, int mid, int R) {
int[] newArr = new int[R - L + 1];
int leftBegin = L;
int rightBegin = mid;
int k = 0;
int result = 0;
while (leftBegin < mid && rightBegin <= R) {
if(arr[leftBegin] > 2 * arr[rightBegin]){
result += mid - leftBegin;
rightBegin++;
}else {
leftBegin++;
}
}
leftBegin = L;
rightBegin = mid;
while (leftBegin < mid && rightBegin <= R) {
newArr[k++] = arr[leftBegin] <= arr[rightBegin] ? arr[leftBegin++] : arr[rightBegin++];
}
while (leftBegin < mid) {
newArr[k++] = arr[leftBegin++];
}
while ((rightBegin <= R)) {
newArr[k++] = arr[rightBegin++];
}
for (int i = 0; i < newArr.length; i++) {
arr[L + i] = newArr[i];
}
return result;
}
}