ACM基础(四):排序之归并排序


图解排序算法(四)之归并排序


一、原理

1. 理解

【分治思想理解:错在以为最小问题是会直接比较两个元素,真正的做法是解部分干脆就不比较,直接划分成单个元素,由并部分比较两个元素及其以上】的确,归并排序的解部分的功能和并部分的功能重复,可以写到一起。

错误理解:

  • 分到不能再分才开始解:归并排序一直划分(递归)到排序两个元素为止。
  • 解只是解最小问题的解:解只是排序两个元素
  • 并不是求解具体的最小问题的,只是负责将子问题的答案如何合并:负责将已经得到的两个排好的部分排序是并。

正确理解:

  • 分到不能再分才开始解:归并排序一直划分(递归)到排序一个元素(不能再划分)为止。
  • 解只是解最小问题的解:归并排序的解不做任何事情
  • 并不是求解具体的最小问题的,只是负责将子问题的答案如何合并:负责将已经得到的两个排好的部分排序是并。

2. 伪代码

  • MERGE-SORT(A, p, r)
// 伪代码
// 数组A,开始下标p,结束下标r
MERGE-SORT(A, p, r)
	// 保证p<q,还可以划分
    if p<r
    // 分成两半排序
    // 选取q,奇数个数就是中间的值的下标,偶数个数就是中间偏左的值的下标
    then	q ← ⌊(p + r) / 2)// 从p到q
    		MERGE-SORT(A, p, q)
    		// 从q+1,到r。q已经在它该在的位置上了。
			MERGE-SORT(A, q + 1, r)
			// 归并部分
        	MERGE(A, p, q, r)
  • MERGE(A, p, q, r)
// 数组A,开始下标p,中间位置的下标q,结束下标r
MERGE(A, p, q, r)
	// 从p到q(包括)共有多少个元素
	n1 ← q - p + 1
	// 从q+1到r(包括)共有多少个元素
	n2 ← r - q
	// 创建两个数组L和R,元素个数是n+1个
	create arrays L[1..n1+1] and R[1..n2+1]
	// L数组是A数组的从p到q(包括)部分
	for i ← 1 to n1
		do L[i] ← A[p + i - 1]
	// R数组是A数组的从q+1到r(包括)部分
	for j ← 1 to n2
		do R[j] ← A[q + j]
	// 正无穷,用于当L、R数组遍历结束时,L[i]、R[j]就是正无穷。
	L[n1 + 1] ← ∞
	R[n2 + 1] ← ∞
	
	// 将L和R数组中的元素按照特定的方式插入到A数组中。
	// L数组的下标,指在L数组最左边(即A数组的p)
	i ← 1
	// R数组的下标,指在R数组最左边(即A数组的q+1)
	j ← 1
	// 遍历A数组中的p到r部分。
	for k ← p to r
    	// 将L[i]和R[j]中较小的元素插入到A中
		// 如果L[i]小于等于R[j]。这个等于在这里,保证了同值的元素保持原来的先后顺序
		do	if	L[i] ≤ R[j]
			// 将更小的元素L[i]插入到A数组中,L数组的下标i右移
			then	A[k] ← L[i]
					i ← i + 1
			// 如果R[i]比L[j]更小
			// 将更小的元素R[i]插入到A数组中,R数组的下标j右移
			else A[k] ← R[j]
				 j ← j+1

3. 图示

  • MERGE-SORT(A, p, r)
    在这里插入图片描述
    考点:递归的顺序。
    【①⑪②】像是树的先序遍历,将问题划分成A和B后,不是同时去再划分A和B,而是一直沿着A这样划分下去。
    【⑤⑨⑩】只要能合并就立即合并,而不是先去划分。

  • MERGE(A, p, q, r)
    在这里插入图片描述
    在这里插入图片描述

二、特点

优点:

  • 外部排序:可以不用一次全部读入
  • 稳定的排序算法:同值的元素保持原来的先后顺序

缺点:

  • 需要额外创建的空间代价

时间复杂度:

  • 最小规模 c=1
  • 划分产生 a=2 个子问题,每个子问题的规模是 1/b=1/2
  • 划分时间 D(n)=无(因为直接传参数⌊(p + r) / 2)⌋就行)
  • 合并时间 C(n)= Θ ( n ) \Theta(n) Θ(n)
    在这里插入图片描述

三、实现

#include <iostream>
using namespace std;

// ACM中的无穷大常量
#define INF 0x3F3F3F3F

void merge(int A[], int p, int q, int r)
{
	// 从p到q(包括)共有多少个元素
    int n1 = q - p + 1;
	// 从q+1到r(包括)共有多少个元素
	int n2 = r - q;

	// 创建两个数组L和R,元素个数是n+1个
    int *L = new int[n1+1];
    int *R = new int[n2+1];
    // L数组是A数组的从p到q(包括)部分
    for (int i = 0; i < n1; i++)
    {
        L[i] = A[p + i];
    }
    // R数组是A数组的从q+1到r(包括)部分
    for (int j = 0; j < n2; j++)
    {
        R[j] = A[q + 1 + j];
    }
    // 正无穷,用于当L、R数组遍历结束时,L[i]、R[j]就是正无穷。
    L[n1] = INF;
    R[n2] = INF;

	// 将L和R数组中的元素按照特定的方式插入到A数组中。
    // i是L数组的下标,指在L数组最左边(即A数组的p)
    // j是R数组的下标,指在R数组最左边(即A数组的q+1)
    // 遍历A数组中的p到r部分。
    for (int i = 0, j = 0, k = p; k <= r; k++)
    {
    	// 将L[i]和R[j]中较小的元素插入到A中
    	// 如果L[i]小于等于R[j]。这个等于在这里,保证了同值的元素保持原来的先后顺序
        if (L[i] <= R[j])
        {
            A[k] = L[i];
            i++;
        }
        // 如果R[i]比L[j]更小
		// 将更小的元素R[i]插入到A数组中,R数组的下标j右移
        else
        {
            A[k] = R[j];
            j++;
        }
    }

	// 释放数组
    delete[] L,R;
}

// 下标从p到q(包括q)
void merge_sort(int A[], int p, int r)
{
	// 保证p<q,还可以划分
    if (p < r)
    {
        int q = (int)((p + r) / 2);
        // 或者 int q = floor((p + r) / 2); 需要#include <math.h>
        
        // 从p到q
        merge_sort(A, p, q);
        // 从q+1,到r。q已经在它该在的位置上了。
        merge_sort(A, q + 1, r);
        // 归并部分
        merge(A, p, q, r);
    }
}

int main()
{
    int A[] = {8, 4, 5, 7, 1, 3, 6, 2};
    int length = sizeof(A) / sizeof(int);
    merge_sort(A, 0, length - 1);
    for (int i = 0; i < length; i++)
    {
        cout << A[i] << ' ';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值