进阶排序算法:归并排序

归并排序

归并排序是利用递归的排序。在递归返回的过程中进行合并排序。递归可以从字面理解,有递:数据在向下传递的过程中切分成越来越小的规模,直到可以直接得出结果;有归:数据在返回时逐渐汇总,得到最终结果。归并的归对应递归的归,就是在递归返回的过程中进行排序。

原理过程:

归并排序采用分治思想,将元素二分后递归到下一层,直到无法再次分割。在返回后将两段数据合并,合并时进行排序。

将数组左起第一个元素标记为L = 0,最后一个标记为R = size - 1,中间元素设定为MID = (L + R) / 2。分割时左侧数据从L到MID,右侧从MID + 1到R。

在归并排序中,递归前进段只是求数据段中点并分割然后传递给下一层,并不会进行排序处理。

递归的基准条件设计为当L == R时返回。

在递归的返回段,将左右两段数据进行合并同时排序。

与快速排序相对比,快排在递归的前进段就已经进行快排分割了,递归返回时数据已经有序;而归并排序在递归的返回段才进行数据合并和排序。

在归并排序的过程中,需要先将排序后的数据存储到新的数组上,然后再复制到数组对应段落。

代码解释:

使用之前的方式生成随机数数组,使用和之前快排相似的接口,根据上面的思路,可以得到归并排序的代码:

cpp 复制代码
#include <iostream>
#include <random>
#include <ctime>

//归并排序合并
void Merge(int arr[],int l, int m, int r)
{
	int* p = new int[r - l + 1];
	int i = l;
	int j = m + 1;
	int index = 0;

	while (i <= m && j <= r)
	{
		if (arr[i] <= arr[j])
		{
			p[index++] = arr[i++];	//执行完赋值语句后对index和i分别++
		}
		else
		{
			p[index++] = arr[j++];
		}
	}

	//两段数据中剩余的数据后缀到p
	while (j <= r)
	{
		p[index++] = arr[j++];
	}

	while (i <= m)
	{
		p[index++] = arr[i++];
	}

	//把合并后的大段数据复制到arr数组的[l, r]区间
	for (i = l, j = 0; i <= r; i++, j++)
	{
		arr[i] = p[j];
	}
	delete[] p;
}

//归并排序递归接口
void MergeSort(int arr[], int begin, int end)
{
	if (begin == end)
		return;
	int mid = (begin + end) / 2;
	//递归前进
	MergeSort(arr, begin, mid);
	MergeSort(arr, mid + 1, end);

	//递归返回后合并排序
	Merge(arr, begin, mid, end);
}

//归并排序接口
void MergeSort(int arr[], int size)
{
	MergeSort(arr, 0, size - 1);
}

int main()
{
	int arr[10];
	srand(time(NULL));
	for (int i = 0; i < 10; i++)
	{
		arr[i] = rand() % 100;
		std::cout << arr[i] << " ";
	}
	MergeSort(arr, 10);
	//换行输出排序后数组
	std::cout << std::endl << "Array Sorted: " << std::endl;
	for (int i = 0; i < 10; i++)
	{
		std::cout << arr[i] << " ";
	}
}

下面是对归并排序代码的分析:

归并排序的接口类型可以根据实际调用情况修改。比如只给了数组大小的话,就要再多写一个接口接入递归。因为对数组的递归往往要给出数据段开始和结束的位置。

在递归前进过程中只是求出数据段中点,并将数据段二分后分别递归到下一层。

在递归返回过程中,得到返回值后才会进行合并排序。这里归并排序处理数组指针,直接在数组对应数据段上进行了排序,不需要处理返回值。

将合并排序的过程抽象到一个函数Merge中。

cpp 复制代码
//归并排序递归接口
void MergeSort(int arr[], int begin, int end)
{
	if (begin == end)
		return;
	int mid = (begin + end) / 2;
	//递归前进
	MergeSort(arr, begin, mid);
	MergeSort(arr, mid + 1, end);

	//递归返回后合并排序
	Merge(arr, begin, mid, end);
}

//归并排序接口
void MergeSort(int arr[], int size)
{
	MergeSort(arr, 0, size - 1);
}

在合并排序过程中,需要一个新的数组来保存排序好的序列。这里在栈空间开辟与处理的数据段等长的数组。设计两个整型i、j分别记录两段数据的开始点。

在两段数据没有遍历完之前,用一个while来比较两个下标处数据的大小,较小的赋值到新数组中保存。由于两段数据分别有序,只需要按顺序比较就可以在合并两段数据的同时得到有序数组。

把剩余的数据添加到新数组的末尾,就得到了合并排序后的数组。

之后把数组赋值给原数组对应位置。

不要忘记删除指针指向的数组内存,释放栈空间。

cpp 复制代码
//归并排序合并
void Merge(int arr[],int l, int m, int r)
{
	int* p = new int[r - l + 1];
	int i = l;
	int j = m + 1;
	int index = 0;

	while (i <= m && j <= r)
	{
		if (arr[i] <= arr[j])
		{
			p[index++] = arr[i++];	//执行完赋值语句后对index和i分别++
		}
		else
		{
			p[index++] = arr[j++];
		}
	}

	//两段数据中剩余的数据后缀到p
	while (j <= r)
	{
		p[index++] = arr[j++];
	}

	while (i <= m)
	{
		p[index++] = arr[i++];
	}

	//把合并后的大段数据复制到arr数组的[l, r]区间
	for (i = l, j = 0; i <= r; i++, j++)
	{
		arr[i] = p[j];
	}
	delete[] p;
}

性能分析:

时间复杂度:

二路归并不存在最好最坏时间复杂度之分,因为在分割数据时严格从中间二分。

空间复杂度:

开辟新的数组所消耗的空间复杂度为O(n),递归消耗的空间复杂度为O(log n)可以忽略。

**稳定性:**稳定

由于归并排序在返回时数据严格按照顺序合并,不会扰乱原本的顺序。

如 2① 5 2② 8 1 在归并排序时

前进段:

2① 5 2② | 8 1

2① 5 | 2② | 8 | 1

2① | 5 | 2② | 8 | 1

返回段:

2① 5 | 2② | 8 | 1

2① 2② 5 | 1 8

1 2① 2② 5 8

相关推荐
遥感知识服务22 分钟前
从局部阈值、双极化到暗地表剔除:NASA OPERA DSWx-S1全球动态水体算法拆解
大数据·人工智能·深度学习·神经网络·算法·机器学习
晚风醉蝶28 分钟前
1-13-TimSort
算法
ZJU_统一阿萨姆37 分钟前
【算子开发】算子融合与Softmax_LayerNorm实现
人工智能·算法·语言模型·硬件架构
叩码以求索3 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
北风toto4 小时前
中缀、前缀、后缀表达式
算法·软件设计师
听取WA声一片(无恶意)4 小时前
CSP-J/CSP-S 深度优先搜索(DFS)完全讲义
c++·算法·深度优先
码流怪侠4 小时前
2026年8月GitHub热榜深度拆解:Agent Skills席卷开源圈,一个“技能包“收割5万星
算法·程序员·github
hold?fish:palm4 小时前
30 两两交换链表中的节点
数据结构·算法·链表
Nil2085 小时前
leetcode 98验证二叉搜索树
算法·leetcode·职场和发展
kkkkkkkkkk_Z5 小时前
学嵌入式和C语言编程数据结构|学习日记Day21:内核链表与队列学习
c语言·数据结构·学习