题目描述:
在这个问题中,您必须分析特定的排序算法----超快速排序。
该算法通过交换两个相邻的序列元素来处理 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 main()
{
int n ;
while(cin>>n,n){
for(int i = 0 ; i < n ; i ++){
cin>>arr[i];
}
cout<<merge(0,n-1)<<endl;
}
return 0;
}
第三:书写归并排序
定义l , r为我们区间的边界,如果左边界大于等于右边界就返回,定义我们的 i 为左边界j 为 右边界的第一个所以为 mid+1 ,定义k 为新数组的第一个位置所以是0;
继续归并排序直到将其分解为一个一个数将他们要更换的次数加起来,完成后返回res。
进入while循环注意我们的条件左边区间不可以超过mid 右边区间不可以超过 r ;如果左边的区间小于等于右边的区间 则证明没有逆序对,则不需要更换位置,之接将其放入新数组中,如果左边区间的值 大于 右边区间的值 则res += mid - i + 1 ,因为逆序对的数量是mid - i + 1,res += mid - i + 1;
这句代码表示如果右半部分的元素 arr[j]
小于左半部分的元素 arr[i]
,那么它与左半部分的剩余元素都构成逆序对。通过计算 mid - i + 1
,得到左半部分剩余元素的数量,即存在与 arr[j]
构成逆序对的数量。将这一数量累加到 res
中,以累计逆序对的总数。
之后检查一下,左右两个部分有没有循环完毕,如果没有就让它继续循环,把数字放入新的数组之中。
最后将tmp数组中刚刚存好的值,再次赋给arr数组。
LL merge(int l , int r){
if(l >= r) return 0;
int mid = (l+r) / 2;
int i = l , j = mid + 1 , k =0;
LL res=merge(l,mid)+merge(mid+1,r);
while(i <= mid and j <= r){
if(arr[i] <= arr[j]){
tmp[k++] = arr[i++];
}else{
tmp[k++] = arr[j++];
res += mid - i + 1;
}
}
while(i <= mid){
tmp[k++] = arr[i++];
}
while(j <= r){
tmp[k++] = arr[j++];
}
for(int i = l , j = 0 ; i <= r ; j++ , i++){
arr[i] = tmp[j];
}
return res;
}
这两个图我给大家标出了i , j ,k的位置,大家可以好好注意注意,对于不理解的位置变化的那个式子,也可以根据这个图自己动手写一些,纸上得来终觉浅,得知此事要躬行
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 5e5+10;
LL arr[N];
LL tmp[N];
// 定义归并排序函数,返回逆序对的数量
LL merge(int l , int r){
if(l >= r) return 0; // 当序列长度为1时,逆序对数量为0
int mid = (l+r) / 2; // 将数组分为两半
// 递归地求解左半部分和右半部分的逆序对数量,并将结果相加
LL res=merge(l,mid)+merge(mid+1,r);
int i = l , j = mid + 1 , k =0; // i为左半部分的指针,j为右半部分的指针,k为临时数组的指针
while(i <= mid and j <= r){
if(arr[i] <= arr[j]){ // 当左半部分的元素小于等于右半部分的元素时,当前元素不会产生逆序对
tmp[k++] = arr[i++]; // 将左半部分的元素放入临时数组中
}else{
tmp[k++] = arr[j++]; // 当右半部分的元素小于左半部分的元素时,当前元素会与左半部分的剩余元素构成逆序对
res += mid - i + 1; // 将逆序对的数量累加上
}
}
// 将剩余的元素放入临时数组中
while(i <= mid){
tmp[k++] = arr[i++];
}
while(j <= r){
tmp[k++] = arr[j++];
}
// 将临时数组的元素复制回原数组
for(int i = l , j = 0 ; i <= r ; j++ , i++){
arr[i] = tmp[j];
}
return res; // 返回逆序对的数量
}
int main()
{
int n ;
while(cin>>n,n){
// 输入数组元素
for(int i = 0 ; i < n ; i ++){
cin >> arr[i];
}
// 调用归并排序函数,并输出逆序对的数量
cout<< merge(0,n-1) << endl;
}
return 0;
}