整体思路先左边从小到大排序,再右边从小到大排序,最后整体排序。首先把数字分为两部分[l,m]和[m+1,r]
,所以主函数调用时l=0,r=length-1
代码分为两部分:
1.排序:不断调用自己,停止条件是l==r
左指针等于右指针
void mergesort(int l,int r,int b[])
{
if(l == r)
return;
int m = (r+l)/2;//l+(r-l)>>1
mergesort(l,m,b);
mergesort(m+1,r,b);
merge(l,m,r,b);
}
2.合并
设置两个指针分别指向左侧第一个数和,右侧第一个数。当while(p1<=m &&p2<=r)
时,可以继续从小到大排序。后面两个while
指若一个排完,另一个还未排完,直接把未排完的数加在后面。
void merge(int l,int m,int r,int b[])
{
int help[B_MAX];
int i = 0;
int p1 = l;
int p2 = m + 1;
while(p1<=m &&p2<=r)
{
help[i++] = (b[p1]>b[p2]? b[p2++]:b[p1++]);
}
while(p1<=m)
{
help[i++] = b[p1++];
}
while(p2<=r)
{
help[i++] = b[p2++] ;
}
for(i = 0; i < r-l+1; i++){
b[l+i] = help[i];
}
}
#include
#include
#define B_MAX 7
using namespace std;
void merge(int l,int m,int r,int b[])
{
int help[B_MAX];
int i = 0;
int p1 = l;
int p2 = m + 1;
while(p1<=m &&p2<=r)
{
help[i++] = (b[p1]>b[p2]? b[p2++]:b[p1++]);
}
while(p1<=m)
{
help[i++] = b[p1++];
}
while(p2<=r)
{
help[i++] = b[p2++] ;
}
for(i = 0; i < r-l+1; i++){
b[l+i] = help[i];
}
}
void mergesort(int l,int r,int b[])
{
if(l == r)
return;
int m = (r+l)/2;
mergesort(l,m,b);
mergesort(m+1,r,b);
merge(l,m,r,b);
}
int main()
{
srand((unsigned)time(NULL));
int b[B_MAX];//={1,8,5,2,7,4,3};
//cout<<"b= ";
for(int i = 0;i < B_MAX;i++)
{
b[i] = rand()%10;
//cout<
}
//cout<
mergesort(0,B_MAX-1,b);
system("pause");
return 0;
}
时间复杂度O(N*logN),额外空间复杂度O(N)
递归行为的复杂度计算公式:T(N) = a*T(N/b) + O(N^d)
a=2
,子过程样本规模为二分之一b=2
。额外代价是对N个数遍历一遍排序d=1
。故时间复杂度为O(N*logN)。在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16
在归并过程中计算小和,实质是如果左数小于右数,则记录
1 3 4|2 5
/
1 3 |4 2|5
/
1|3
1.在1|3归并中,1<3,产生小和1;
2.在1 3|4归并中,p1指向1,p2指向4。1<4,记录小和1,p1指向3,3<4,继续记录小和3;
3.右侧2|5,2<5,记录小和2;
4.1 3 4 | 2 5中,p1指向1,p2指向2。1<2,则2右侧大于2的数都构成小和,记录小和2个1;
p1移到3处,3>2不记录,p2右移到5,3<5,由于5右侧没有数了,所以记录小和1个3;p1继续右移,4<5,再继续记录小和1个4;
5.小和 = 1+1+3+2+21+13+1*4 = 16
result负责记录当前小和,当左侧小时,把右侧当前到右侧末尾的个数乘以左侧较小的数得到小和。
while(p1<=m&&p2<=r)
{
result += b[p1]<b[p2] ? ((r-p2+1)*b[p1]):0;
help[i++] = b[p1]>b[p2] ? b[p2++]:b[p1++];
}
#include
#define B_Length 5
using namespace std;
int merge(int l,int m,int r,int b[])
{
int help[B_Length];
int p1 = l;
int p2 = m+1;
int i = 0;
int result = 0;
while(p1<=m&&p2<=r)
{
result += b[p1]<b[p2] ? ((r-p2+1)*b[p1]):0;
help[i++] = b[p1]>b[p2] ? b[p2++]:b[p1++];
}
while(p1<=m)
{
help[i++] = b[p1++];
}
while(p2<=r)
{
help[i++] = b[p2++];
}
for (i = 0; i < r-l+1; i++)
{
b[i+l] = help[i];
}
return result;
}
int mergesort(int l,int r,int b[])
{
if(l == r)
return 0;
int m = l+((r-l)>>1);
return mergesort(l,m,b)+mergesort(m+1,r,b)+merge(l,m,r,b);
}
int main()
{
int b[B_Length]={
1,3,4,2,5};
int a = 0;
a = mergesort(0,B_Length-1,b);
system("pause");
return 0;
}
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印逆序对个数。
逆序对问题的实质是记录左比右大的次数。与小和问题相比,只需要记录左侧较大数当前位置到左侧最终位置的个数即可。
while(p1<=m&&p2<=r)
{
result += b[p1]>b[p2] ? (m-p1+1):0;
help[i++] = b[p1]>b[p2] ? b[p2++]:b[p1++];
}
#include
#define B_Length 5
using namespace std;
int merge(int l,int m,int r,int b[])
{
int help[B_Length];
int p1 = l;
int p2 = m+1;
int i = 0;
int result = 0;
while(p1<=m&&p2<=r)
{
result += b[p1]>b[p2] ? (m-p1+1):0;
help[i++] = b[p1]>b[p2] ? b[p2++]:b[p1++];
}
while(p1<=m)
{
help[i++] = b[p1++];
}
while(p2<=r)
{
help[i++] = b[p2++];
}
for (i = 0; i < r-l+1; i++)
{
b[i+l] = help[i];
}
return result;
}
int mergesort(int l,int r,int b[])
{
if(l == r)
return 0;
int m = l+((r-l)>>1);
return mergesort(l,m,b)+mergesort(m+1,r,b)+merge(l,m,r,b);
}
int main()
{
int b[B_Length]={
2,5,1,3,4};
int a = 0;
a = mergesort(0,B_Length-1,b);
system("pause");
return 0;
}