为什么逆序对要用归并排序呢
首先我们来看归并排序的原理
归并模板
void merage(int l,int r)
{
if(l>=r)return ;
int mid=l+r>>1;
merage(l,mid); //分割左边区间
merage(mid+1,r); //分割右边区间
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r) //块与块内部元素的比较后合并
{
if(a[i]<=a[j])
tmp[k++]=a[i++];
else
tmp[k++]=a[j++];
}
while(i<=mid)tmp[k++]=a[i++]; //合并残留
while(j<=r)tmp[k++]=a[j++];
for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j] //每次利用临时数组tmp排好的数据赋给
} //数组a到最最开始的一层递归a就是排
//序好的数组
看下面这几行代码
if(l>=r)return ;
int mid=l+r>>1;
merage(l,mid);
merage(mid+1,r);
我们发现函数体开始就是不断的制造分界点 分割区域然后调用函数递归
所以我们脑子里面可以大概有一个被分割完成的画面 因为mid取值为中值;
编辑
下面的区域会越来越小 然后精准分割到每一个区域为一个元素
然后我们再看以下代码
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r) //块与块内部元素的比较后合并
{
if(a[i]<=a[j])
tmp[k++]=a[i++];
else
tmp[k++]=a[j++];
}
它们的功能就是实现块与块之间内部元素的比较
注:tmp数组存在的意义就是暂时保留排序好的数组称为临时数组
例如:
左区域:2,3,5 (i)
右区域: 1,4,6(j)
等等 为什么出现的都是有序的区域 你是不是故意这样写的 容易理解
那我们回顾一下上面 (我真的服了你这个老6)归并的递归调用会一直调用到一个区域为一个元素 然后通过这块代码合并出来的
通过以上代码可变为1,2,3,4,5
???那6呢 因为while循环已经不符合执行条件了 i 已经超出范围了 那么这么办 别急
看以下代码
while(i<=mid)tmp[k++]=a[i++]; //合并残留
while(j<=r)tmp[k++]=a[j++];
这是收尾工作 把那可恶的老6给找出来
然后最终收尾
for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j]
这是把每一段合并好的元素从临时数组tmp赋值给a 方便下一次对数组a的操作
最后一次就是排序好的数组 不相信的宝子们可以找一个大于50个元素的数组试一下
好的 这就是归并排序
???
那逆序对呢
归并排序逆序对的应用
快看这个老6
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r) //块与块内部元素的比较后合并
{
if(a[i]<=a[j])
tmp[k++]=a[i++];
else
tmp[k++]=a[j++];
}
我们仔细观察这行代码 tmp[k++]=a[j++]
它是什么条件下成立的呢
显然 左区域中某个元素大于右区域中某个元素的时候
然而这不就是逆序对存在的条件吗
例 2,3,4,5,1
然后你灵机一动 心想:这样的话,那它交换一次不就是一个逆序对吗,那我写个变量加一不就行了吗 这时你的嘴角微微一扬 自信的拿起键盘 定义了一个public变量 劈里啪啦
long long merage(int l,int r)
{
if(l>=r)return ;
int mid=l+r>>1;
merage(l,mid);
merage(mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r)
{
if(a[i]<=a[j])
tmp[k++]=a[i++];
else
tmp[k++]=a[j++];
res++;
}
while(i<=mid)tmp[k++]=a[i++]; //合并残留
while(j<=r)tmp[k++]=a[j++];
for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j];
return res;
}
你为函数写上了返回值 你还细心的把返回值的范围写大了
然后就报红了 然后你就不是当时的嘴角上扬而是痛苦面具
真相只有一个
然而这就是我们要解决的问题
当res++面对这样的数据时
左区域:3,4 (i)
右区域: 1,2(j)
显得非常的无力
我们都能看出逆序对的数量是4个 但是res++计算不了
但是我们发现 如果a[i]>a[j]的话 那么左区域中i~mid的范围都是大于mid 因为内部都是有序排列
哦 那就好办了
求i~mid之间元素的个数呗
res=res+mid-i+1;
7到10有几个元素 10-7+1=4 7没有的8 9 10 算上它本身就是4个 尾项减去首项+1;
你开开心心的提交了代码
通过了
然后你匪夷所思的看着最后一行代码
for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j];
这个还有用吗
你灵机一动 哦 这个是输出归并排序结果的 我不输出结果我只是用他的计算过程 删了删了
心想:又少一行代码 程序有高级不少啊
然后就又红了 破口大骂 这是干什么的呀 我又不输出
现在回顾一下上面(真的服了你这个老六了)
注意这行代码
res=res+mid-i+1;
它成立的条件就是需要内部有序,而你删除的代码就是变成有序序列的步骤
否则你运算的就一直是原序列
现在 你有纳闷了 我改变顺序会不会影响到逆序对的数量呀
答案是肯定的
肯定不会的
你计算的都是区域跟区域间的
就好像 左区域的5不管在哪个索引位置都比右区域的2要大 永远构成逆序对 排序只是一种更高效的算法实现