利用归并排序法计算一个序列里有多少逆序对数(详细讲解)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用归并排序法计算一个序列里有多少逆序对数(详细讲解)相关的知识,希望对你有一定的参考价值。

前言


 

  今天遇到求逆序对的问题,经过一番思索之后,特意来总结一下。因为也学习到了很多方法,以前自己一些百思不得其解的问题也有了解答。

 

正文


 

先上一个简单的问题:

技术分享

   分析:题目中说使用插入排序,也就是在排序过程中计算交换的次数,按照插入排序的原理,先定第一个,再定前两个的顺序,以此类推,只要交换了,我的次数就加一,但实际上,我们一直按照原始序列的顺序一直在往后走,所以(好,重点来了)我们要插入的就是前面比我大的数字前面的位置,也就是说,我需要交换的次数就是前面比我大的数字的个数,那么我考虑那就没必要进行交换了,直接进行和前面的数字进行比较就可以了啊,只要前面有比你现在所比较的数大,则加一。其实这很像我们线代学过的逆序数,就是求逆序数的个数。

  接下来就是写代码:

 1 #include<stdio.h>
 2 int main() {
 3     int n,m;
 4 
 5     scanf("%d\n",&n);
 6 
 7     int count = 0;
 8     for(int i=0; i < n; i++) {
 9         scanf("%d\n",&m);
10         int ch[m];
11         for(int j=0; j < m; j++) {
12             scanf("%d",&ch[j]);
13         }
14 
15         for(int k=1; k < m; k++) {
16             for(int l=0; l < k; l++) {
17                 if(ch[k] < ch[l]) count++;
18             }
19         }
20     }
21     printf("%d\n",count);
22 
23     return 0;
24 }

 

这里的代码就是普普通通的代码,通俗易懂。当然,两个循环那里可以进行算法的优化,有心的读者自己去尝试吧。或者说看接下来的内容也可以收获另一种求逆序数的方法。 

 

  好,接下来,我们讲述重点的问题,相信很多人都可以解决前面的问题,但接下来的问题就不是那么容易了,需要思考与一些代码技巧。

技术分享

   注意:Hint : 直接使用兩層迴圈來找答案的話會超過系統時間限制。

   所以就必须寻求算法复杂度低的算法,按照提示,说在归并的过程当中计算逆序数对,首先要熟悉归并排序的原理,再结合问题来看。于是我在纸上进行了演算,先不断地进行二分,然后就两个两个相比较,就是分为两组,每组一个数,如果后面这个比前面的大,那就是一个逆序数对,然后就加一,并且将小的数字放在前面,于是合并,就得到了包含两个数的有序的一组数。接下来就是比较每组两个数的比较,如果第二组的第一个数大于第一组的第一个数,就加二,因为它比前面这组所有数都小。然后归并。以此类推。原则就是:如果后面这组数的某个数比前面这组的第i个数小,则逆序对数加上(mid-i+1)。

  虽然明白了原理,对于归并不熟悉的同学,我觉得还是比较难的,特别是其中的一些技巧。

  不多说。先分析代码如何写,先写框架:

 1 #include<stdio.h>
 2 
 3 int count = 0;//逆序数对
 4 void mergeSort(int lo,int hi) {
 5     
 6 }
 7 
 8 int main() {
 9     int N;
10     scanf("%d",&N);
11 
12     for(int i=0; i < N; i++) {
13         scanf("%d",&ch[i]);
14     }
15 
16     mergeSort(0,N-1);//归并排序
17 
18     printf("%d\n",count);
19     return 0;
20 }

 

这部分框架应该都能看懂。接下来讲述归并。首先原理就是先二分,分别排序,后归并。

 1 void mergeSort(int lo,int hi) {
 2     if(lo < hi) {
 3 
 4         int mid =( lo + hi ) / 2;//另一种写法:int mid = ( lo + hi )>> 1;(学过计算机组成的应该知道,最终代码都会转换成二进制,二进制数字向右移一位代表除以2)
 5        //二分排序
 6         mergeSort(lo,mid);
 7         mergeSort(mid + 1,hi);
 8        //归并
 9         Merge(lo,mid,hi);
10     }
11 }

其实这差不多也是个框架,只不过注意一下 lo < hi 这个条件。

然后重点在于归并这部分,设置标记点,i = lo  和 j = mid+1  循环的条件应该是

1 int i = lo;
2 int j = mid + 1;
3 while(i <= mid&&j <= hi) {
4 
5 }

 

如果后面前面这组数的第i个数大于后面某个数,count就加mid-i+1。当然归并时需要一个临时数组来存储这些改变位置的数,

 1 int i = lo;
 2 int j = mid + 1;
 3 int x = lo;
 4 
 5 while(i <= mid&&j <= hi) {
 6     if ( ch[i] > ch[j]) {
 7         count +=  mid - i + 1;
 8         temp[x++] = ch[j++];
 9     }
10      else {
11         temp[x++] = ch[i++];
12     }
13 }

 

当然,这还有个要注意的地方,如果前面这组数已经排完了,然后后面这组数还没完就已经退出了循环,那这个临时数组就没有归并所有的数进来,就不完整。此时就应该加上

1 while(i <= mid) temp[x++] = ch[i++];
2 while(j <= hi)  temp[x++] = ch[j++];

 然后当然我们还要把这个临时数组的值又返回到原来的数组中,以便于这个数组在下一轮进行归并。

1 for(int k = lo; k <= hi ; k++)
2         ch[k]  = temp[k];

 

好,这样,我们就完成整个代码的编写

这是完整的源代码:

 1 #include<stdio.h>
 2 
 3 void Merge(int ,int ,int );
 4 void mergeSort(int ,int );
 5 
 6 int ch[20000],temp[20000];//最大有20000个数,注意这里要是全局变量,易于使用。
 7 int count = 0;//逆序数,一定要是全局变量,这样就可以无论怎么递归都会一直加。原先的想法就是递归中返回逆序对的数,不断累加,实现起来比这个困难。这个直接就是全局变量,方便简洁。
 8 
 9 void mergeSort(int lo,int hi) {//递归函数里不断二分排序,归并。
10     if(lo < hi) {
11 
12         int mid =( lo + hi ) / 2;
13 
14         mergeSort(lo,mid);
15         mergeSort(mid + 1,hi);
16 
17         Merge(lo,mid,hi);
18     }
19 }
20 
21 void Merge(int lo,int mid,int hi) {//进行归并
22     int i = lo;
23     int j = mid + 1;
24     int x = lo;
25 
26     while(i <= mid&&j <= hi) {
27         if ( ch[i] > ch[j]) {
28             count+=  mid - i + 1;
29             temp[x++] = ch[j++];
30         } else {
31             temp[x++] = ch[i++];
32         }
33     }
34 
35     while(i <= mid) temp[x++] = ch[i++];
36     while(j <= hi)    temp[x++] = ch[j++];
37 
38     for(int k = lo; k <= hi ; k++)
39         ch[k]  = temp[k];
40 
41 }
42 int main() {
43     int N;
44     scanf("%d",&N);
45 
46     for(int i=0; i < N; i++) {
47         scanf("%d",&ch[i]);
48     }
49 
50     mergeSort(0,N-1);
51 
52     printf("%d\n",count);
53     return 0;
54 }

 

总结


  这里带给我最大的收获就是count是全局变量,因此才可以在不断的递归中一直累加,我原先的想法就是在递归中看能不能返回逆序对的个数,或者在参数中间加入逆序对的个数一直传递。这次终于得到了解答,还有这个归并时他创建的临时数组也很巧妙,最终又赋值给原数组。最棒的就是递归这部分,以前老是理不清,想不清,看来以后得多用用递归。

 

 

 2016-02-25  12:37:30

 

以上是关于利用归并排序法计算一个序列里有多少逆序对数(详细讲解)的主要内容,如果未能解决你的问题,请参考以下文章

归并排序法---题目

归并排序(归并排序求逆序对数)--16--归并排序--Leetcode面试题51.数组中的逆序对

逆序对的三种求法(归并排序,树状数组,线段树)

51nod1107(逆序对数&归并排序)

微软面试题:求一个序列的逆序对数

归并排序 逆序对