首先给出一个定理:
在一个排列当中,如果一对数的前后位置与大小顺序相反(即前面的数大于后面的数)那么就称为一个逆序,一个排列当中逆序的总数称为这个排列的逆序数。
一个排列的逆序数等于该排列转化为自然排序(从小到大)的最小次数。
题目概述:
在这个问题中,您必须分析特定的排序算法----超快速排序。
该算法通过交换两个相邻的序列元素来处理 n 个不同整数的序列,直到序列按升序排序。
对于输入序列 9 1 0 5 4,超快速排序生成输出 0 1 4 5 9。
您的任务是确定超快速排序需要执行多少交换操作才能对给定的输入序列进行排序。
输入格式:
输入包括一些测试用例。
每个测试用例的第一行输入整数 n,代表该用例中输入序列的长度。
接下来 n 行每行输入一个整数 ai,代表用例中输入序列的具体数据,第 i 行的数据代表序列中第 i 个数。
当输入用例中包含的输入序列长度为 0 时,输入终止,该序列无需处理。
输出格式:
对于每个需要处理的输入序列,输出一个整数 op,代表对给定输入序列进行排序所需的最小交换操作数,每个整数占一行。
数据范围:
0≤n<500000,一个测试点中,所有 n 的和不超过 500000。
0≤ai≤999999999
示例:
输入样例:
5
9
1
0
5
4
3
1
2
3
0
输出样例:
6
0
算法思路:
本题首先观察数据范围,可以估计如果使用int则可能会超出范围,那么定义变量的时候使用long类型;
每次交换相邻元素使得序列变得有序,那么我最初想到的是使用冒泡排序,但是时间复杂度是O(n^2),这样肯定会过不了,那么想到在一个排列当中该排列的逆序数等于该排列化为自然排序(从小到大)的最小次数,那么可以使用时间复杂度为O(nlogn)的归并排序。
使用归并排序可以对该序列进行递归操作,预先定义一个temp数组用来存储前后区域的排序结果,然后定义两个指针,分别指向两个区域最头部分,一次比较两个区域填入temp数组当中,然后将temp数组排序后重新修改原数组对应区域,直到递归条件结束递归。
算法实现:
import java.util.Scanner;
class Main{
//将变量定义在类体当中
public static long[] arr = new long[500000];
public static long count = 0;
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
//表示命令行当中还有输入
//获取输入序列的长度
int num = scanner.nextInt();
if(num==0){
break;
}
//获取每个元素存入到数组当中
for(int i=0;i<num;i++){
arr[i] = scanner.nextLong();
}
//进行归并排序计算交换次数
merge_sort(arr,0,num-1);
System.out.println(count);
//下一次重新计算交换次数
count = 0;
}
}
//定义方法实现归并排序
public static void merge_sort(long[] arr,int l,int r){
//递归结束条件
if(l>=r){
return;
}
int mid = (l+r)>>1;
//递归进行归并排序
merge_sort(arr,l,mid);
merge_sort(arr,mid+1,r);
//定义一个临时数组用来存储两部分排序之后的结果
long[] temp = new long[r-l+1];
//定义两个指针,分别指向将要排序的两部分起始位置
int i = l;
int j = mid+1;
int k = 0;
//当两个部分都没有遍历完
while(i<=mid&&j<=r){
if(arr[i]>arr[j]){
//前面大后面小需要进行交换序列
temp[k++] = arr[j++];
count += mid-i+1;
}else{
temp[k++] = arr[i++];
}
}
//后一个序列已经遍历完了
while(i<=mid){
temp[k++] = arr[i++];
}
//前一个序列遍历完
while(j<=r){
temp[k++] = arr[j++];
}
//将排序后的区域重新赋值给arr数组
int index = 0;
for(int m=l;m<=r;m++){
arr[m] = temp[index++];
}
}
}