归并排序及模板
1.思想
归并排序也是基于分治法的思想。
1. 确定分界点(一般为中间点mid=(l+r)/2)使得整个数组被划分为left和right区间。
2. 递归排序left和right区间。
3. 归并-合二为一。进而将整个数组排序完成。(注意:在归并的时候,left和right区间都应该是有序的。归并之后的数组仍是一个有序数组)。
在上述步骤中,最难的部分就是归并的部分。

在这里,我们采用双指针算法来进行归并,具体思想如下:
假设,我们需要归并的数组为a和b(已经有序)。
1. 设定指针i,指向数组a的第一个位置(数组a的最小值)。同时设定指针j,指向数组b的第一个位置(数组b的最小值)。同时开辟数组res,用来存储归并之后的数组。
2. 将i和j指向的元素进行比较,如果i小于等于j,那么就把i指向的元素放到res中,同时i往下走一格,反之同理。
3. 当a和b至少有一个遍历完成时,双指针算法结束,但是需要把没有遍历完的数组的剩余元素接到res的后面,至此双指针算法结束。
在上述过程中,虽然会花费额外空间,但是时间复杂度良好。是O(n)
结合上述的算法,我们来分析一下归并排序的时间复杂度。首先,整个数组在第一次递归中,被划分为了2个区间。第二次递归,被划分为了4个区间,.....最后被划分为n个长度只有1的区间(此时每个区间都是天然有序的)。那么,从1个长度为n的区间,到n个长度为1的区间,中间划分了logn次。然而,划分区间之后,就形成了一颗二叉树。当我们处理二叉树的每一层时(归并),花费的时间复杂度都是O(n)。
综上所述,归并排序的时间复杂度是O(nlogn)。
快速排序的平均时间复杂度也是O(nlogn),但是最坏时间复杂度为O(n²)。

2.模板
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
3.例题与习题
地址:https://www.acwing.com/problem/content/789/
#include <iostream>
#include <cstdio>
using namespace std;
void merge_sort(int q[],int l,int r){
if(l >= r){
return;
}
int mid = (l+r) >> 1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int res[100010];
int i = l;
int j = mid + 1;
int k = 0;
while(i <= mid && j <= r){
if(q[i] <= q[j]){
res[k++] = q[i++];
}else{
res[k++] = q[j++];
}
}
while(i <= mid){
res[k++] = q[i++];
}
while(j <= r){
res[k++] = q[j++];
}
for(i=l,k=0;i<=r;i++,k++){
q[i] = res[k];
}
}
int main(){
int n;
scanf("%d",&n);
int q[100010];
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
merge_sort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
return 0;
}
地址:https://www.acwing.com/problem/content/790/
该算法的求解思路如下:
1. 我们假设归并排序在给某一区间排序的过程中,就可以把该区间的逆序数找到,那么我们可以将逆序数对的所在区间情况分为三种:
1. 逆序数对都在区间左边,那么进行递归即可。即:merge_sort(q,l,mid)
2. 逆序数对都在区间右边,那么进行递归即可。即:merge_sort(q,mid+1,r)
3. 如果逆序数对在两个区间(一个在左区间,一个在右区间),那么统计逆序对的数量。
由于归并排序的特点,我们可以发现,当进行递归后,最终会达到区间只有一个数字的情况,即逆序数对在左右两个区间。当统计完局部区间的逆序数对之后,再对整体区间的逆序数对进行统计即可。
2. 我们如何统计逆序数对呢?这时需要用到归并排序的双指针算法,假设i指向左区间的第一个位置,j指向右区间的第一个位置,tmp代表将两个区间归并之后的数组。那么,如果q[i] > q[j] 那么i之后的数,一直到mid,都大于q[j]。因此,q[j]的逆序对数量为(mid-i+1),将q[j]存到tmp数组之后,再让j往后移动一个位置,再进行比较。如果q[i] <= q[j],那么不构成逆序数对。将q[i]存到tmp数组之后,再让i往后移动一个位置,再进行比较。当遍历完这两个数组其中之一或全部时,再将所求得的逆序对数量进行累加即可。之后按照归并排序的流程,我们就可以求得整个区间的逆序数对。
#include <iostream>
#include <cstdio>
using namespace std;
long long result = 0;
long long merge_sort(int q[],int l,int r){
if(l >= r){
return 0;
}
int mid = (l+r) >> 1;
int i = l;
int j = mid + 1;
int tmp[100010];
long long count = 0;
int k = 0;
result += merge_sort(q,l,mid) + merge_sort(q,mid+1,r);
while(i <= mid && j <= r){
if(q[i] > q[j]){
tmp[k++] = q[j++];
count += mid - i + 1;
}else{
tmp[k++] = q[i++];
}
}
while(i <= mid){
tmp[k++] = q[i++];
}
while(j <= r){
tmp[k++] = q[j++];
}
for(i = l,j = 0;i<=r;i++,j++){
q[i] = tmp[j];
}
return count;
}
int main(){
int n;
int q[100010];
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
result += merge_sort(q,0,n-1);
printf("%lld",result);
return 0;
}
作者:gao79138
链接:https://www.acwing.com/
来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!