通过·归并(合并)排序·求逆序对个数

本文介绍了一种利用归并排序算法计算数组中逆序对数量的方法,通过两次代码实现,解决了数据溢出问题,适用于大规模数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目要求:利用归并排序求得排序过程中出现逆序对的个数。如输入数组长度为5的序列,分别为9·1·0·5·4,得到逆序对的个数为6.可通过以下代码得到结果。(可是POJ不通过,这点我搞不明白,可否有高手看看代码处是否出错,不胜感谢!)

 
  
1 #include < stdio.h >
2 #include < stdlib.h >
3   #define MAX 50000
4   /* *
5   * 0 <= p <= q < r, subarray array[p..q] and array[q+1..r] are already sorted.
6   * the merge() function merges the two sub-arrays into one sorted array.
7   */
8 int count = 0 ;
9 void Merge( int array[], int p, int q, int r)
10 {
11 int i,k;
12 int begin1,end1,begin2,end2;
13 int * temp = ( int * )malloc((r - p + 1 ) * sizeof ( int ));
14 begin1 = p;
15 end1 = q;
16 begin2 = q + 1 ;
17 end2 = r;
18 k = 0 ;
19
20 while ((begin1 <= end1) && ( begin2 <= end2))
21 {
22 if (array[begin1] <= array[begin2])
23 {
24 temp[k] = array[begin1];
25 begin1 ++ ;
26
27 }
28 else
29 {
30 temp[k] = array[begin2];
31 begin2 ++ ;
32 // count++;
33 /*
34 *
35 *求逆序对,当array[begin1]中的数比array[begin2]中的数大时;
36 *那么可以推出此数后面的数和array[begin2]这个数大;
37 */
38 count += end1 + 1 - begin1;
39
40 }
41 k ++ ;
42
43 }
44
45 while (begin1 <= end1)
46 {
47 temp[k ++ ] = array[begin1 ++ ];
48
49 }
50
51 while (begin2 <= end2)
52 {
53 temp[k ++ ] = array[begin2 ++ ];
54
55 }
56
57 for (i = 0 ;i < (r - p + 1 );i ++ )
58 array[p + i] = temp[i];
59
60 free(temp);
61
62 }
63 /* *
64 归并操作
65 */
66 void MergeSort( int array[], int first, int last)
67 {
68 int mid = 0 ;
69 if (first < last)
70 {
71 mid = (first + last) / 2 ;
72 MergeSort(array,first,mid);
73 MergeSort(array,mid + 1 ,last);
74 Merge(array,first,mid,last);
75 }
76
77 }
78
79 int main()
80 {
81 int i;
82 int array[MAX];
83 int input;
84 scanf( " %d " , & input);
85 while (input != 0 )
86 {
87 for (i = 0 ;i < input;i ++ )
88 {
89 scanf( " %d " , & array[i]);
90 }
91 MergeSort(array, 0 ,input - 1 );
92 /* for(i=0;i<input;i++)
93 {
94 printf("%d ",array[i]);
95 }
96 */
97 printf( " %d\n " ,count);
98 count = 0 ;
99
100 scanf( " %d " , & input);
101 }
102
103 return 1 ;
104 }

Input:
5
9
1
0
5
4
3
1
2
3
10
2
1
6
5
7
9
11
12
3
8
2
11
11

0


Output:
   1|6
   2|0
   3|11
   4|0

以上4项测试结果没错误。(只通过这几项测试不能证明什么,真的...)

************************************************************分割线**********************************************************

 `通过分析别人的代码和老师的提示,上面的代码运行是没错的,不过当测试的数据长度在500,000数量级上时,上述代码数据溢出。

`我们通过Int64类型来定义整形数(Int64 值类型表示值介于 -9,223,372,036,854,775,808 到 +9,223,372,036,854,775,807 之间的整数。)

`实现代码如下:

 

 
  
1 #include < stdio.h >
2
3
4   /* *
5   * 0 <= p <= q < r, subarray array[p..q] and array[q+1..r] are already sorted.
6   * the merge() function merges the two sub-arrays into one sorted array.
7   */
8 __int64 count;
9 __int64 a[ 500005 ];
10 __int64 temp[ 500005 ];
11   // 防止数据溢出
12   void Merge( int p, int q, int r)
13 {
14 int i,k;
15 int begin1,end1,begin2,end2;
16 k = p;
17 begin1 = p;
18 end1 = q;
19 begin2 = q + 1 ;
20 end2 = r;
21
22
23 while ((begin1 <= end1) && ( begin2 <= end2))
24 {
25 if (a[begin1] <= a[begin2])
26 {
27 temp[k] = a[begin1];
28 begin1 ++ ;
29
30 }
31 else
32 {
33 temp[k] = a[begin2];
34 begin2 ++ ;
35 // count++;
36 /*
37 *
38 *求逆序对,当array[begin1]中的数比array[begin2]中的数大时;
39 *那么可以推出此数后面的数和array[begin2]这个数大;
40 */
41 count += end1 - begin1 + 1 ;
42
43 }
44 k ++ ;
45
46 }
47
48 while (begin1 <= end1)
49 {
50 temp[k ++ ] = a[begin1 ++ ];
51
52 }
53
54 while (begin2 <= end2)
55 {
56 temp[k ++ ] = a[begin2 ++ ];
57
58 }
59
60 for (i = p;i <= r;i ++ ) // 把合并后的数组还原
61
62 a[i] = temp[i];
63
64 }
65
66
67
68 /* *
69 归并操作
70 */
71 void MergeSort( int first, int last)
72 {
73 int mid = 0 ;
74 if (first < last)
75 {
76 mid = (first + last) / 2 ;
77 MergeSort(first,mid);
78 MergeSort(mid + 1 ,last);
79 Merge(first,mid,last);
80 }
81
82 }
83
84 int main()
85 {
86 int i;
87 int input;
88 scanf( " %d " , & input);
89 while (input != 0 )
90 {
91 count = 0 ;
92 for (i = 0 ;i < input;i ++ )
93 {
94 scanf( " %I64d " , & a[i]);
95 }
96
97 MergeSort( 0 ,input - 1 );
98
99 printf( " %I64d\n " ,count);
100
101
102
103 scanf( " %I64d " , & input);
104 }
105
106
107 return 1 ;
108 }

 

 

 

PS:上面代码的改进之处

1,使用了int64定义整数类型,使得输入的整数型能满足系统测试的数据长度。

2,提前定义了备用数组temp,而不需要像第一个代码中每次归并都要开辟一个数组空间。(有好处有坏处,在这里是好处)

3,通过把数组a和temp变为全局变量,而不需要在方法间传递数组参数。

4,到此止步,但自己还是不太完全了解归并排序中的递归过程,只是模糊的认识而已。

 

 

转载于:https://www.cnblogs.com/SCAU_que/articles/1847510.html

<think>好的,我现在需要帮助用户了解如何用归并排序来计算数组中的逆序对个数。首先,我得回忆一下归并排序的基本原理,因为用户可能已经知道,但可能需要复习一下。归并排序是分治法的典型应用,通过将数组分成子数组,排序后再合并。在这个过程中,合并阶段可以统计逆序对的数量。 接下来,我要明确逆序对的定义:如果前面一个数字大于后面的数字,那么这两个数字就组成一个逆序对。比如数组[3,1,2]中,逆序对(3,1)(3,2),所以总数是2。 接下来,我需要思考如何在归并排序合并过程中统计逆序对归并排序合并两个已排序的子数组时,会比较左右两边的元素。当左边的元素大于右边的元素时,这里可能存在多个逆序对。例如,如果左边子数组是[4,5],右边是[1,3],当合并时,4比1大,那么4和后面的所有右边元素都构成逆序对,这里就是2个逆序对(4和1,4和3)。 这时候,我需要考虑如何具体实现这一点。在合并过程中,每次从左右两个子数组中取出较小的元素放入临时数组。当右半部分的元素被取出时,说明当前左半部分剩余的所有元素都比这个右半部分的元素大,因此可以累加逆序对的数量。比如,当合并到右半部分的元素1时,左半部分剩下的元素是4和5,所以此时逆序对的数量增加2。 然后,我需要考虑分治法的步骤。递归地将数组分成左右两部分,分别计算左右内部的逆序对,然后在合并时计算跨越左右的逆序对。最后将这三部分的结果相加。这样就能在O(n log n)的时间复杂度内解决问题。 接下来,我需要考虑具体的代码实现。归并排序通常需要一个辅助数组来合并,而在统计逆序对时,需要在合并过程中添加计数逻辑。比如,当右边的元素被选入辅助数组时,计数器加上左半部分剩余元素的个数。 还要注意边界条件和递归终止条件,比如当子数组长度为1时,逆序对数量为0。另外,需要考虑数组索引的处理,避免越界错误。 然后,我需要验证这个方法的正确性。例如,对于一个简单的测试用例,如[3,1,2],归并排序合并过程中应该正确统计出2个逆序对。或者更复杂的例子,比如[7,5,6,4],逆序对总数应该是5。 最后,需要总结这个过程,确保用户能够理解分治法的应用,以及如何在归并排序合并阶段统计逆序对的数量。同时,可能还要比较归并排序与其他方法(如暴力法)的时间复杂度,说明归并排序的优势。</think>### 使用归并排序计算逆序对个数的原理与实现 #### 核心思想 归并排序的分治特性天然适合统计逆序对。在合并两个有序子数组时,通过比较左右两部分的元素,可以**直接计算跨越左右子数组的逆序对数量**,再递归统计子数组内部的逆序对[^1][^3]。 #### 关键步骤 1. **分治递归** 将数组分成左右两部分,分别递归计算左右子数组内部的逆序对数量: $$ 总逆序对数 = 左子数组逆序对数 + 右子数组逆序对数 + 合并时发现的逆序对数 $$ 2. **合并计数** 合并两个有序子数组时,当发现右子数组元素小于左子数组元素时,说明当前左子数组中所有未处理的元素都与该右子数组元素构成逆序对。例如: ``` 左子数组:[5,7], 右子数组:[4,6] 当右子数组元素4被取出时,左子数组剩余元素5、7都 > 4,此时累加2个逆序对 ``` #### 代码实现 ```python def merge_sort_count(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, cnt_left = merge_sort_count(arr[:mid]) right, cnt_right = merge_sort_count(arr[mid:]) merged, cnt_cross = merge(left, right) return merged, cnt_left + cnt_right + cnt_cross def merge(left, right): merged = [] i = j = cnt = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) cnt += len(left) - i # 核心计数点 j += 1 merged += left[i:] merged += right[j:] return merged, cnt ``` #### 算法特性 - 时间复杂度:$O(n\log n)$,与归并排序时间复杂度一致[^2] - 空间复杂度:$O(n)$,来自合并时的临时数组[^3] - 优势:相比暴力解法的$O(n^2)$时间复杂度,效率显著提升
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值