《数据结构》归并排序算法

归并排序(Merge Sort)是一种有效的排序算法,使用了“分治法”的策略来将一个大问题分解成更小的子问题,并通过合并这些子问题的解来得到原问题的解。归并排序具有稳定性,适用于各种数据结构。

基本思想:

归并排序,其基本思想可以分为以下几个步骤:

  1. 分解(Divide)

    • 将待排序的数组分成两半。这个过程递归地进行,直到每个子数组只有一个元素或为空为止,因为一个单独的元素已经是有序的。
  2. 征服(Conquer)

    • 对每个子数组递归地进行排序。这意味着将每个子数组继续分解,直到分解到基本的单元(单个元素或空数组)。
  3. 合并(Combine)

    • 将已排序的子数组合并成一个更大的有序数组。合并操作需要将两个已排序的子数组合并成一个有序的子数组,保证最终得到的数组仍然是有序的。

过程图示:

假设我们有一个数组 [38, 27, 43, 3, 9, 82, 10],我们将应用归并排序:

图示说明: 

  1. 分解

    • 将 [38, 27, 43, 3, 9, 82, 10] 分成 [38, 27, 43] 和 [3, 9, 82, 10]
    • 对 [38, 27, 43] 和 [3, 9, 82, 10] 分别递归排序。
  2. 征服

    • 对 [38, 27, 43] 进一步分解成 [38] 和 [27, 43],然后对 [27, 43] 进行分解和排序。
    • 对 [3, 9, 82, 10] 进一步分解成 [3, 9] 和 [82, 10],然后对每部分进行排序。
  3. 合并

    • 合并 [27] 和 [43] 成 [27, 43]
    • 合并 [38] 和 [27, 43] 成 [27, 38, 43]
    • 合并 [3] 和 [9] 成 [3, 9]
    • 合并 [82] 和 [10] 成 [10, 82]
    • 合并 [3, 9] 和 [10, 82] 成 [3, 9, 10, 82]
    • 最后合并 [27, 38, 43] 和 [3, 9, 10, 82] 成 [3, 9, 10, 27, 38, 43, 82]

 简易代码描述:

// 合并函数
void merge(int a[], int p, int q, int s, int t) {
	// 分配足够的内存来存储合并结果
	int* b = (int*)malloc((t - p + 1) * sizeof(int));
	if (b == NULL) {
		perror("malloc fail!");
		return;
	}
	
	int i = p, j = s, k = 0;// 从 0 开始填充 b
	// 合并两个有序子数组到临时数组 b 中
	while ((i <= q) && (j <= t)) {
		if (a[i] < a[j]) 
			b[k++] = a[i++]; // 将较小的元素添加到 b 中
		else 
			b[k++] = a[j++]; // 将较小的元素添加到 b 中
	}
	// 如果左侧子数组还有剩余元素,拷贝到 b 中
	while (i <= q) {
		b[k++] = a[i++];
	}
	// 如果右侧子数组还有剩余元素,拷贝到 b 中
	while (j <= t) {
		b[k++] = a[j++];
	}
	
	// 将合并结果拷贝回原数组 a 中
	for (i = p; i <= t; i++) {
		a[i] = b[i - p];// 拷贝到原数组,注意偏移量
	}
	
	// 释放临时数组的内存
	free(b);
}

// 归并排序函数
void merge_sort(int a[], int i, int j) {
	if (i < j) {
		// 计算中间索引
		int k = (i + j) / 2;
		// 对左侧子数组进行排序
		merge_sort(a, i, k);
		// 对右侧子数组进行排序
		merge_sort(a, k + 1, j);
		// 合并两个已排序的子数组
		merge(a, i, k, k + 1, j);
	}
}

代码解释:

merge 函数:

  1. 内存分配

    • int* b = (int*)malloc((t - p + 1) * sizeof(int));
    • 为合并结果分配一个临时数组 b,大小为 t - p + 1
  2. 合并过程

    • int i = p, j = s, k = 0;
    • 初始化三个指针:i 指向左子数组的开始,j 指向右子数组的开始,k 指向临时数组 b 的位置。
    • 比较 a[i] 和 a[j],将较小的元素放入 b[k],然后相应地移动指针 i 或 j,并递增 k
    • 当一个子数组中的元素被用尽后,将剩余的元素从另一个子数组拷贝到 b 中。
  3. 拷贝回原数组

    • for (i = p; i <= t; i++) { a[i] = b[i - p]; }
    • 将合并后的结果从临时数组 b 拷贝回原数组 a
  4. 释放内存

    • free(b);
    • 释放临时数组 b 的内存。

merge_sort 函数:

  1. 递归分解

    • if (i < j) {
    • 递归地将数组分解为两个子数组,直到每个子数组只包含一个元素。
    • int k = (i + j) / 2;
    • 计算中间索引 k,将数组 a 从 i 到 k 和 k+1 到 j 进行递归排序。
  2. 合并排序后的子数组

    • merge(a, i, k, k + 1, j);
    • 在左右子数组排序完成后,调用 merge 函数将它们合并成一个有序的数组。

归并排序特点:

  • 时间复杂度:归并排序的时间复杂度是 (O(n \log n)),这是因为每次分解将问题规模减少一半,而合并两个有序子数组的时间复杂度是 (O(n))。
  • 空间复杂度:归并排序需要额外的空间来存储临时数组,所以空间复杂度是 (O(n))。
  • 稳定性:归并排序是稳定的排序算法,即相同值的元素在排序后的顺序保持不变。
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

data_structure_wr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值