归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,
在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。
因此,可以在归并排序中的合并过程中计算逆序数。
源代码(C语言)
#include<stdio.h> #include<stdlib.h> _int64 inversionCount=0; void merge_and_count(int *a,int p,int q,int r){ //从零开始计数 int i,j,t; i=p; j=q+1; t=0; int *templist = (int *)malloc(sizeof(int) * (r-p+1)); while(i<=q && j<=r){ if(a[i]<a[j]){ templist[t++] = a[i]; i++; } else{ templist[t++] = a[j]; inversionCount+=(q+1-i); j++; } } while(i<=q){ templist[t++] = a[i++]; } while(j<=r){ templist[t++] = a[j++]; } t=0; for(int k=p;k<=r;k++) a[k] = templist[t++]; } void merge_sort(int *a,int p,int r){ if(p<r){ int q=(p+r-1)/2; merge_sort(a,p,q); merge_sort(a,q+1,r); merge_and_count(a,p,q,r); } }
Quick_sort算法来数逆序数(这个多说一些)
该算法主体与quick_sort相似,给定一个序列,先找到一个分割点splitter,然后quick_sort关于splitter节点两半部的交换过程之间查找一个相对逆序数,包括三部分:左半部的相对逆序数,右半部的相对逆序数,左右两边的相对逆序数。分成两半之后,依次递归,得出结果。以{2 7 8 3 6 9 4 5 10}为例,令splitter=(left+right)/2,分为两部分S- 和 S+:
左半部(从右往左splitter–>left)
如果data[i]>data[splitter],数出位于该点右边小于或等于data[splitter]的元素的个数,将该计数(如图加粗画底线部分计数)加入逆序数计数InversionCount,并把该元素加入S+,如果data[i]<data[splitter],操作与quick_sort相似。
2 | 7 | 8 | 3 | 6(splitter) | 9 | 4 | 5 | 10 |
例如data[i] = 8 > data[splitter] = 6 ; 黑体部分计数为2,因此遍历到8时InversionCount += 2.
右半部(从左往右splitter–>right)
如果data[i] < data[splitter],数出位于该点左边小于或等于data[splitter]的元素的个数,将该计数(如图加粗画底线部分计数)加入逆序数计数InversionCount,并把该元素加入S-,如果data[i] > data[splitter],操作与quick_sort相似。
2 | 7 | 8 | 3 | 6(splitter) | 9 | 4 | 5 | 10 |
例如,data[i] = 5 < data[splitter] = 6,黑体部分计数为2,因此遍历到5时InversionCount += 2.
交叉部分:
数出splitter左半部比data[splitter]大的元素个数lr,数出splitter右半部比data[splitter]小的元素的个数rl,然后InversionCount +=lr * rl。例子中InversionCount += 2 * 2.
2 | 7 | 8 | 3 | 6(splitter) | 9 | 4 | 5 | 10 |
程序代码(C语言)
#include<stdio.h> #include<stdlib.h> _int64 InversionCount=0; void merge_count(int *data,int & splitter,int left,int right){ if(left > splitter || splitter > right) return; int pivot= data[splitter]; int *tempLL,*tempLR,*tempRL,*tempRR; int ll=0,lr=0,rl=0,rr=0; int i; bool flag1=false,flag2=false; //左边求相对逆序数,遍历顺序从splitter往左 if(left < splitter){ flag1 = true; tempLL = (int *)malloc(sizeof(int) * (splitter - left)); tempLR = (int *)malloc(sizeof(int) * (splitter - left)); for(i=splitter - 1; i>=left; i--) if(data[i] < pivot) tempLL[ll++] = data[i]; //ll为left部分目前比splitter小但离splitter最近的点的个数 else{ tempLR[lr++] = data[i]; InversionCount += ll+1; //ll+1 为该点对于left部分比splitter小但处于该点右边的点的逆序数 } } //右边求相对逆序数,遍历顺序从splitter往右 if(right>splitter){ flag2 =true; tempRL = (int *)malloc(sizeof(int) * (right - splitter)); tempRR = (int *)malloc(sizeof(int) * (right - splitter)); for(i=splitter+1; i<=right; i++) if(data[i] > pivot) tempRR[rr++] = data[i]; //rr为right部分目前比splitter大但离splitter最近的点的个数 else{ tempRL[rl++] = data[i]; InversionCount += rr+1; //rr+1为该点对于right部分比splitter大但处于该点左边的点的逆序数 } } //加上left和right之间的相对逆序数 InversionCount += lr * rl; //将新的序列拷贝到原数组,注意拷贝的顺序 int k=left; for(i=ll-1; i>=0; i--) data[k++] = tempLL[i]; for(i=0; i<rl; i++) data[k++] = tempRL[i]; splitter = k; data[k++] = pivot; for(i=lr-1; i>=0; i--) data[k++] = tempLR[i]; for(i=0; i<rr; i++) data[k++] = tempRR[i]; if(flag1) delete tempLL,tempLR; if(flag2) delete tempRL,tempRR; } void quicksort_count(int *data,int left,int right){ if(left>=right) return; int splitter = left; merge_count(data, splitter, left, right); quicksort_count(data,left,splitter-1); quicksort_count(data,splitter+1,right); }
所有基于比较的排序算法中,快速排序的平均效率是最高的(尽管它性能不太稳定),以上两种求逆序数算法是对归并排序和快速排序算法的稍稍改进,
以下是对十个包含100000个各不相同随机自然数文件的实验结果:
可以看出快排求逆序数的算法效率更好一些。
转载于:https://www.cnblogs.com/BeWater/archive/2013/03/03/2941488.html