一.问题描述
给出一个序列,求这个序列中存在的逆序对数量。
逆序对定义:在一个序列A中,假设存在下标i,j, i < j, 且 Ai > Aj, 则称{Ai,Aj} 是一对逆序对
例如;序列(4,3,1)逆序对有(4,3),(4,1),(3,1)共三个。
二.解决方案
最简单的方法是从序列的第一个数开始,依次遍历它后面的n-1个数字并进行对比找出逆序对,接着让第二个数和他后面的每一位数进行比较,依次类推,很显然该方法的时间复杂度是O(n^2)的。
而采用分治法其时间复杂度为O(nlogn),分治法求解步骤,第一步,把所求序列分为两个等大的左右子序列,递归的求左右子序列中的逆序对,第二部在合并过程中再去计算两个左右子序列所产生的逆序对。
对于第一步中各个左右子序列中的逆序对我们只需要简单的遍历对比就可以找到。对于第二步前提是我们在合并之前首先保证两个左右子序列分别已经排好序,在合并的过程中,我只需同时从左右序列的第一个关键字开始遍历两个序列,每次发现左序列的元素比右序列的元素大,就说明该左序列的元素及其后的元素都能与右序列的该元素构成逆序对,那么两个子序列逆序对数目之和再加上这两个子序列合并后的逆序对数目就等于原数组的逆序对数目。
刚好归并排序可以使得两个左右子序列在合并之前已经保持有序,所以所弄清楚归并排序对我们采用分治法求逆序对帮助很大,在弄明白归并排序后,只需要在归并排序中添加少量代码来计录逆序对个数即可完成。
三.代码
#include "stdio.h"
int count = 0; //记录逆序对数量
void Merge(int r[], int r1[], int low, int mid, int high) // 合并子序列
{
int i = low, j = mid + 1, k = low;
//int b;
while (i <= mid && j <= high) {
if (r[i] <= r[j]) { // 取较小者放入r1[k]中
r1[k++] = r[i++];
}
else {
count += mid - i + 1;// 若左子序列中的数1大于右子序列的数2,则数1后面的数都大于数2
r1[k++] = r[j++];
}
}
while (i <= mid) // 若第一个子序列没处理完,则进行收尾处理
r1[k++] = r[i++];
while (j <= high)
r1[k++] = r[j++];
}
void MergeSort(int r[], int low, int high) { // 进行归并排序
int mid, r1[1000], i;
if (low == high)
return;
else {
mid = (low + high) / 2; // 划分
MergeSort(r, low, mid); // 递归求左子序列中逆序对
MergeSort(r, mid + 1, high); // 递归求右子序列中逆序对
Merge(r, r1, low, mid, high); // 合并
for (i = low; i <= high; i++)
r[i] = r1[i];
}
}
int main() {
int length;
int r[100] = { 0 };
printf("请输入数组长度\n");
scanf("%d", &length);
printf("请输入需要求逆序对的一组序列\n");
for (int j = 0; j < length; j++)
{
scanf("%d", &r[j]);
}
MergeSort(r, 0, length - 1); // 三个参数分别为所求数组、起始下标、截止下标
printf("\n一共 %d个逆序对\n", count);
}