求逆序对问题用归并排序的时间复杂度比暴力算法更低。
假设有一个数组{8,1,2,5,7,4,3,6}
首先归并排序第一次对数组进行分割 8 1 2 5 7 4 3 6
二次分割 8 1 25 74 36
三次分割 8 1 2 5 7 4 3 6
第一次合并 18 25 74 36 reorder[1]=2, order[1]=2
//用reorder[i]来记录第i次合并中存在的逆序对数,order[i]来记录第i次合并中存在的顺序对数。
第二次合并 1258 3467 reorder[2]=5, order[2]=3
第三次合并 12345678 reorder[3]=6, order[3]=10
那么数组{8,1,2,5,7,4,3,6}中存在的逆序对就等于reorder[1]+reorder[2]+reorder[3]=13
将数组{8,1,2,5,7,4,3,6}每2^2个为一组进行翻转{5,2,1,8,6,3,4,7}
首先归并排序第一次对数组进行分割 5 2 1 8 6 3 4 7
二次分割 5 2 18 63 47
三次分割 5 2 1 8 6 3 4 7
第一次合并 25 18 36 47 reorder[1]=2, order[1]=2
第二次合并 1258 3467 reorder[2]=3, order[2]=5
第三次合并 12345678 reorder[3]=6, order[3]=10
那么数组{5,2,1,8,6,3,4,7}中存在的逆序对就等于reorder[1]+reorder[2]+reorder[3]=11
由此我们可以观察到对数组每2^2个进行翻转时,reorder[1]和order[1]进行了互换,reorder[2]和order[2]亦是如此。
所以对数组每2^i个进行翻转时,我们可以把1~i的reorder和order数组元素互换即可继续通过计算reorder数组的累加和来求得数组的逆序对数。
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int N = 1 << n;
int[] a = new int[N];
int[] b = new int[N];//用来存储数组的逆序,对逆序的数组进行一次归并排序可以直接得到order数组
int[] order = new int[n + 1];//为了便于计算,所以设置order下标是1~n,因此数组大小为n+1
int[] reorder = new int[n + 1];
for (int i = 0; i < N; i++)
{
a[i] = sc.nextInt();
b[N - i - 1] = a[i];
}
MergeSort(a, 0, N - 1, reorder, n);//对整个数组进行归并排序,n表示对reorder[1]~reorder[n]进行初始化
MergeSort(b, 0, N - 1, order, n);//对整个逆序数组进行归并排序,完成对order[1]~order[n]的初始化
int m = sc.nextInt();
while(m-- > 0)
{
int count = 0;
int q = sc.nextInt();
for (int i = 1; i <= q; i++) //像之前讲的,将1~q的reorder[i]和order[i]进行互换
{
int temp = reorder[i];
reorder[i] = order[i];
order[i] = temp;
}
for (int i = 1; i <= n; i++)
{
count+= reorder[i];//累加reorder数组,求得对数组中每2^q个元素进行翻转后的逆序对数
}
System.out.println(count);
}
}
public static void MergeSort(int[] a , int left, int right, int[] reorder, int index)
{
if(left < right)
{
int mid = (right + left) / 2;
MergeSort(a, left, mid, reorder,index - 1);
MergeSort(a, mid + 1, right, reorder,index -1);
if(a[mid] > a[mid+1])//如果a[mid]<=a[mid+1],则原数组有序,不需要合并
Merge(a, left, right,reorder, index);
}
}
public static void Merge(int[] a, int left, int right,int[] reorder, int index)//index表示对reorder[index]进行初始化
{
int mid = (right + left) / 2;
int Length1 = mid - left + 1;
int Length2 = right - mid;
int[] l = new int[Length1];//存储a[left]~a[mid]
int[] r = new int[Length2];//存储a[mid+1]~a[right]
System.arraycopy(a, left, l, 0, Length1);//对l进行初始化
System.arraycopy(a, mid + 1, r, 0, Length2);//对r进行初始化
int i = 0;
int j = 0;
int c= 0;
int k = left;
while(i < Length1 && j < Length2)
{
if(l[i] <= r[j])
{
a[k] = l[i];
i++;
}
else
{
a[k] = r[j];
j++;
c += Length1 - i;//当l[i]>r[j]时,因为l是递增序列,所以l[i]~l[Length1-1]均>r[j],所以有Length1-i个元素大于r[j]
}
k++;
}
System.arraycopy(l, i, a, k, Length1 - i);//前面归并排序MergeSort中调用Merge合并的条件是a[mid]>a[mid+1],因为当a[mid]<=a[mid+1]时说明原数组有序,无需合并。l[Length1-1]>r[Length2-1],即l数组的最大值大于r数组的最大值,所以当r中的数全部进入a数组后,l数组中仍有剩余。
reorder[index] += c;
}
}