我们来看这样一道题目:
给出逆序数的定义:在数列A={}中,如果有一组数(i,j)满足
>
,且i<j,那么这组数称为一个逆序,数列A中逆序的数量就称为逆序数。
简单来说,逆序的这组数就是其数值大小和其先后顺序相反
现给出数组A与其长度n,要求返回其逆序数的值
解法一
题解要求我们找到所有的逆序,观察题目,我们很容易想到,可以先锚定一个下标较小的数,然后向后遍历数组,每找到一个数值比它小的元素,就相当于是找到了一组逆序。不难发现,这就相当于冒泡排序。我们在冒泡排序的过程中,每交换一次,就相当于是找到一组逆序。也就是说,冒泡排序的总交换次数就是数组的逆序数,这种解法的时间复杂度为O()
当数组长度较大的时候,冒泡排序的效率就很低了,那么我们能否找到效率更高的算法呢?
解法二
在解法一中,我们仅把目光放在“一个”下标较小的数身上,那么,为了提高效率,我们是否可以一次性把目光锚定在更多下标较小的数上呢?同时,我们是否可以不遍历后面全部的元素,仅遍历部分元素,就把全部数值比我们所锚定元素小(或大)的元素个数给找出来呢?
想要回答第一个问题,我们势必要将数组划分为两个区间;
而对于第二个问题,我们可以想到,若当后面的数组有序,则可以实现部分遍历找全部元素的目的
综合这两个想法,我们可以发现,这和归并排序的方式十分相似
假定给出数组A={2,5,7,3,6,9,8,4,1,5}
将其分为两个子数组:
C={2,5,7,3,6} , D={9,8,4,1,5}
假定我们已求出C与D各自的逆序数
由于C的元素下标肯定比D小,那么对于C的元素而言(假设对7来说),D中存在n个(这里为3个)比它小的数,C的逆序数就再加3
现将其排序:
C={2,3,5,6,7} , D={1,4,5,8,9}
那么对于D的每一个元素(称之为d),我们只需要遍历C,直到找到比d大的元素(称之为c),那么数组C中c及其之后的元素个数即为元素d所对应的逆序数,具体计算方法为:
记数组C的长度为length,则length=mid+1,设元素c的下标为i(其实就是对应指针i),那么逆序数+=length-i(即为元素c及其之后的元素个数)
对于数组A,我们可以将其分为数组C,D进行求解,那么想要求解数组C,D自身的逆序数,我们只需要将其再次划分,直到划分为子数组内只有一个元素为止。这种递归方式也是和归并排序完全相同的
至此,我们就可以利用归并排序中的局部有序性求出数组的逆序数,该算法的时间复杂度为O(nlogn),空间复杂度为O(n)
实现代码如下:
int MergeAndCal(int* nums, int* temp, int left, int mid, int right) {
int ans = 0;
int i = left;
int j = mid + 1;
int k = left;
int length = mid + 1;
while (i < mid + 1 && j < right + 1) {//从这里开始是合并,我们加入计数的代码
if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
}
else{
ans += length - i; //相比与正常的合并,就加多了这一行代码
temp[k++] = nums[j++];
}
}
while (i < mid + 1) {
temp[k++] = nums[i++];
}
while (j < right + 1) {
temp[k++] = nums[j++];
}
for (k= left; k < right+1; k++) {
nums[k] = temp[k];
}
return ans;
}
int ReverseNum(int* nums, int* temp, int left, int right) {
int res1 = 0, res2 = 0, res3 = 0;
if (left < right) {
int mid = left + (right - left) / 2;
res1=ReverseNum(nums, temp, left, mid);//第一个子数组自身的逆序数
res2=ReverseNum(nums, temp, mid + 1, right);//第二个子数组自身的逆序数
res3 = MergeAndCal(nums, temp, left, mid, right);//两个子数组合并后新增的逆序数
return res1 + res2 + res3;//答案为总和
}
return 0;//数组只有一个元素时,其逆序数为0
}
int BubbleSort(int* nums, int numsSize) {//开一个冒泡排序来检验结果正确性
int ans = 0;
for (int i = 0; i < numsSize-1; i++) {
for (int j = i+1; j < numsSize ; j++) {
if (nums[i] > nums[j]) {
ans++;
}
}
}
return ans;
}
int main() {
int nums1[18] = { 4,2,6,8,7,2,54,3,2,15,14,16,98,10,72,35,20,74 };
int nums2[18] = { 4,2,6,8,7,2,54,3,2,15,14,16,98,10,72,35,20,74 };
int* temp = (int*)malloc(sizeof(int) * 18);
int ans1 = ReverseNum(nums1, temp, 0, 17);
int ans2 = BubbleSort(nums2, 18);
printf("归并排序计数结果:%d\n冒泡排序计数结果:%d\n", ans1,ans2);
return 0;
}