归并排序基本思想:归并排序采用分治法的思想,将数组划分为若干子区间分别排序,再合并子区间。在合并阶段,若当前处理的区间是[l,r],需要将两个有序子序列[l,mid]和[mid+1,r]合并回原数组[l,r]位置。
归并排序函数的整个流程:
1、创建归并排序函数,求出mid中间值、以及left和right
2、分别将这两个区间的值拷贝到两个数组中(方便通过下标调用),左区间和右区间里面没排好序时又会不断调用自身,通过调整传入的参数来实现将左右区间都排好序,直至将每一个区间(左区间和右区间)都排好序。
3、比较两数组中的值,用i代表左区间的指针,用j代表右区间的指针,两者所指向的值不断进行比较,谁小谁就先存入数组中,之后小的那个指针向后顺延,两者所指向的值再不断进行比较,直至所有的值比较完毕。
4、对特殊情况的处理,若只有一个数时,我们直接返回即可;若所排序数组中有两个数时,可以直接将这两个数进行比较即可,不必再去递归调用。
实例理解:理解了基本流程之后,我们根据一个例子来加深对归并排序的理解:若我们想对[8,3,2,9,7,1,5,4]使用归并排序,想要将其从小到大进行排序。
分解阶段(自顶向下):我们先将这个数组进行拆解,不断递归调用,将区间范围不断缩小,下图为所拆解的流程:
合并阶段(自底向上):将分解阶段所得出的每个小区间进行排序,排好序之后再将其合并,最终得到合并好且排好序的两个左右区间。
流程文字描述:
合并左半部分
-
合并
[8]
和[3]
→[3, 8]
-
合并
[2]
和[9]
→[2, 9]
-
合并
①比较[3,8]
和[2,9]
→[2, 3, 8, 9]
3
和2
→ 取2
②比较 3
和 9
→ 取 3
③比较 8
和 9
→ 取 8
④剩余 9
→ 直接追加
合并右半部分
-
合并
[7]
和[1]
→[1, 7]
-
合并
[5]
和[4]
→[4, 5]
-
合并
①比较[1,7]
和[4,5]
→[1, 4, 5, 7]
1
和4
→ 取1
②比较 7
和 4
→ 取 4
③比较 7
和 5
→ 取 5
④剩余 7
→ 直接追加
下图为合并的流程:
最终合并:将所得到的两个区间再次进行排序合并
流程文字描述:
合并左半部分 [2, 3, 8, 9]
和右半部分 [1, 4, 5, 7]
→ [1, 2, 3, 4, 5, 7, 8, 9]
-
比较
2
和1
→ 取1
-
比较
2
和4
→ 取2
-
比较
3
和4
→ 取3
-
比较
8
和4
→ 取4
-
比较
8
和5
→ 取5
-
比较
8
和7
→ 取7
-
剩余
8
和9
→ 直接追加
下图为最终合并的流程:
完整代码:
#include <bits/stdc++.h>
using namespace std;
int a[500005],L[500005],R[500005];
void sortfunc(int l,int r){
if(l==r){//判断只有一个数时的情况
return;
}else if(l==r-1){//判断有两个数时的情况
if(a[l]>a[r]){
//若左边的数大于右边的数,直接用swap函数交换即可
swap(a[l],a[r]);
}
}else{//若有多个数的情况
int mid=(l+r)/2;//求出中间位置
sortfunc(l,mid);//对左区间进行递归调用
sortfunc(mid+1,r);//对右区间进行递归调用
int num1=mid-l+1;//求出左区间的长度
int num2=r-mid;//求出右区间的长度
for(int i=0;i<num1;i++){
L[i]=a[l+i];//将左区间中的值读入数组L中
}
for(int i=0;i<num2;i++){
R[i]=a[mid+1+i];//将右区间中的值读入数组R中
}
//执行第三步,将排好序的左右数组复制到一个数组中
L[num1]=2e9;
R[num2]=2e9;//不用再去处理出界问题
int i=0,j=0;//初始化
for(int k=l;k<=r;k++){
if(L[i]>R[j]){//若i指向的值大于j指向的值,则将R[j]赋值给a[k]
a[k]=R[j];
j++;//将角标向后移动
}else{
a[k]=L[i];
i++;
}
}
}
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
sortfunc(0,n-1);
for(int i=0;i<n;i++){
cout<<a[i]<<" ";
}
return 0;
}
代码中所注意的点:k是从l开始的,这样的话保证了在k从l开始是为了精准地将排好序的左右子数组合并回原数组的目标区间[l,r],确保归并排序的正确性和数据的一致性。
比如,假设l=2,r=5,当前需将L和R合并到a[2...5]。k从2开始,第一次循环处理a[2],第二次处理a[3],依此类推,直到a[5]。这样能确保[l,r]区间被完全覆盖,元素正确合并。
for(int k=l;k<=r;k++){
if(L[i]>R[j]){//若i指向的值大于j指向的值,则将R[j]赋值给a[k]
a[k]=R[j];
j++;//将角标向后移动
}else{
a[k]=L[i];
i++;
}
}