二路归并排序
二路归并排序有两个过程:分与和。
如上图:
(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
© 把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对
Java代码如下:
public class Day0429 {
@Test
public void test1(){
int[] arr = {4, 2, 6, 1, 3, 8, 5, 9};
mergeSort(arr, 0, arr.length-1);
showArr(arr);
}
//归并排序 参数1:待排数组 参数2:最左边的位置 参数3:最右边的位置
public static void mergeSort(int[] arr, int left, int right){
if(arr == null){
return;
}
if(left < right) {
//找中间位置进行划分
int mid = (left+right)/2;
//对左子序列进行递归归并排序
mergeSort(arr, left, mid);
//对右子序列进行递归归并排序
mergeSort(arr, mid+1, right);
//“合”。 进行归并
merge(arr, left, mid, right);
}
}
//将两个有序的序列合并为一个有序的序列,待合并的序列为arr[left,mid] 和 arr[mid+1,right]
public static void merge(int[] arr, int left, int mid, int right){
/**
1.步骤一:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.步骤二:设定两个变量,初始值分别为两个已经排序序列的起始位置;
3.步骤三:比较以两个变量为下标的元素,选择相对较小的元素放入到合并空间,并使其下标值为下一位置下标;
4.重复步骤三直到左下标超过右下标;
5.将另一序列剩下的所有元素直接复制到合并序列尾;
**/
int[] tempArr = new int[arr.length];
int tempIndex = left;
int leftStart = left;
int rightStart = mid+1;
while(leftStart <= mid && rightStart <= right){
if(arr[leftStart] < arr[rightStart]){
tempArr[tempIndex++] = arr[leftStart++];
} else {
tempArr[tempIndex++] = arr[rightStart++];
}
}
//如果左边剩下了,直接加到数组里面
while(leftStart <= mid) {
tempArr[tempIndex++] = arr[leftStart++];
}
//如果右边剩下了,直接加到数组里面
while(rightStart <= right) {
tempArr[tempIndex++] = arr[rightStart++];
}
//将排好序的这部分,赋值到原数组的对应部分
while(left<=right){
arr[left] = tempArr[left];
left++;
}
}
private static void showArr(int[] arr) {
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
现在来看剑指offer的第34题
题目描述: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。
思路1: 我们第一反应便是对每个数字,判断它后面的元素是不是比它小,从而构成逆序。这样的方法是可行的,但是在牛客网提交的时候,提示说超时。
思路2: 我们刚刚学了二路归并排序,其实这道题可以稍微对二路归并算法进行改造。在二路归并的算法的合的时候,是两个排好序的序列进行比较。如下图,我们现在合并两个序列。第一次1和2比较,1放在新的数组第一个位置(新的数组即为合并后的数组)。然后第二次比较,发现4比2大,那么这就是一个逆序。同时,4之后的所有数字都比4大(这个是排好序的序列)。所以此时比2大逆序实际有: mid - i +1个。也就是说当前面的数组的元素和后面数组的元素进行比较较大时,逆序数增加:mid-i+1个。
代码如下:
public class Solution {
private int cun;
public void Merge(int[] arr,int start,int end){
if(start >= end) return;//递归的终止条件
int mid = (start+end)/2;
Merge(arr,start,mid);
Merge(arr,mid+1,end);
MergeOne(arr,start,mid,end);
}
public void MergeOne(int[] arr,int start,int mid,int end){
//进行排序
int [] temp = new int[end-start+1];
int k=0,i=start,j=mid+1;
while(i<=mid && j<=end){
if(arr[i]<=arr[j]){//没有逆序
temp[k++] = arr[i++];
}else{//有逆序
temp[k++] = arr[j++];
cun = (cun+(mid-i+1))% 1000000007;
}
}
//计算没有归并完的情况
while(i<=mid){
temp[k++] = arr[i++];
}
while(j<=end){
temp[k++] = arr[j++];
}
for(int l=0;l<k;l++){
arr[start+l] = temp[l] ;
}
}
public int InversePairs(int [] array) {
Merge(array,0,array.length-1);
return cun;
}
}