2.归并排序:稳定的分治排序

2.归并排序:稳定的分治排序

当两个组数据已经有序,我们可以通过如下方式(以下简称归并大法)让两组数据快速有序

image-20230626144329637

我们可以依次从两组中取最前面的那个最小元素依次有序放到新的数组中,然后再把新数组中有序的数据拷贝到原数组中,快速完成排序。

image-20230626144435737

依靠这种思想,提出了如下的排序方法!

具体步骤
对于下面这一组待排序的数组

image-20230626144520673

先以中间为界,把其均分为A 和B 两个数组(如果是奇数个,允许两组数相差一个)

image-20230626144557135

如果A 和B 两组数据能够有序,则我们可以通过上面的方式让数组快速排好序。

此时,A 组有4 个成员,B 组有5个成员,但两个数组都无序,然后我们可以采用分治法继续对A组和B组进行均分,以A 组为例,又可以均分A1和A2两个组如下:

image-20230626144729476

均分后,A1组和A2组仍然无序,继续利用分治法细分,以A1组为例,A1又可分成如下两组

image-20230626144822813

数组细分到一个元素后,这时候,我们就可以采用归并法借助一个临时数组将数组A1 有序化!A2 同理!

image-20230626151805351

依次类推,将A1组和A2组归并成有序的A组, B 组同理!

image-20230626155501691

最后,将A和B组使用归并大法合并,就得到了完整的有序的结果!

image-20230626155616769

2.1 核心思想:分治(先递归排序,再合并)​

  • 与快排区别:快排 “先分区再排序”,归并 “先排序再合并”;归并需额外辅助数组,但保证稳定性。

2.2 三步实现法​

步骤 1:确定分界点 mid

  • 固定取 “中间下标”:mid = (l+r)/2(无需选值,仅划分区间)。

步骤 2:递归排序左右区间

  • 左区间:merge_sort(q, l, mid);

  • 右区间:merge_sort(q, mid+1, r);

  • 递归后,左区间和右区间均为有序数组(关键前提)。

步骤 3:归并(合并两个有序数组,核心难点)

  • 目标:将两个有序数组(左[l,mid]、右[mid+1,r])合并为一个有序数组,存入原数组。

  • 实现步骤(双指针法):

    1. 初始化:

      • 双指针:i = l(左数组起点),j = mid+1(右数组起点);

      • 辅助数组tmp(存合并结果),计数器k = 0(tmp的下标);

    2. 比较合并:

      • 若q[i] q[j](左数组当前元素更小),将q[i]存入tmp,i++;

      • 否则,将q[j]存入tmp,j++;

      • 重复,直到i > mid或j > r(一个数组遍历完);

    3. 处理剩余元素:

      • 若左数组未遍历完(i ,将剩余元素q[i..mid]存入tmp;

      • 若右数组未遍历完(j r),将剩余元素q[j..r]存入tmp;

    4. 复制回原数组:将tmp中的有序元素覆盖q[L..R]。

  • 示例模拟(左数组[1,3,5],右数组[2,4,6]):

    1. 初始:i=0,j=3,tmp=[];
    2. q[0]=1 2 → tmp=[1],i=1;
    3. q[1]=3 > q[3]=2 → tmp=[1,2],j=4;
    4. q[1]=3 4 → tmp=[1,2,3],i=2;
    5. q[2]=5 > q[4]=4 → tmp=[1,2,3,4],j=5;
    6. q[2]=5 5]=6 → tmp=[1,2,3,4,5],i=3(左数组遍历完);
    7. 处理右数组剩余元素6 → tmp=[1,2,3,4,5,6];
    8. 复制tmp到原数组,合并完成。

2.3 归并排序代码模板(C++)

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];
}

2.4 关键注意点​

  • 稳定性:归并是稳定排序(相同值的相对位置不变,因q[i] ]时优先取左数组元素);

  • 空间复杂度:O (n)(辅助数组tmp),而快排仅需 O (logn) 栈空间(递归调用);

  • 时间复杂度:固定 O (nlog2n)(无论数据分布,均需递归 logn 层,每层合并 O (n));

  • 效率对比:老师实验显示,快排、归并与 C++ 标准库sort(混合排序)效率接近,模板可靠性高。

posted @ 2026-03-25 17:33  CodeMagicianT  阅读(5)  评论(0)    收藏  举报