进阶排序算法:归并排序

归并排序

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

原理过程:

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

将数组左起第一个元素标记为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

相关推荐
wearegogog1232 小时前
光伏发电系统最大功率跟踪(MPPT)算法 Matlab 实现指南
开发语言·算法·matlab
Tisfy2 小时前
LeetCode 3783.整数的镜像距离:数学
数学·算法·leetcode·题解
水蓝烟雨2 小时前
0010.三数之和
数据结构·算法·leetcode
ShineWinsu2 小时前
技术架构设计
数据结构
努力进修2 小时前
【java-数据结构】Java优先级队列揭秘:堆的力量让数据处理飞起来
java·开发语言·数据结构
啥咕啦呛2 小时前
跟着AI学Java第2天:Java基础语法巩固
java·python·算法
csuzhucong3 小时前
螺旋归纳DP
算法
qeen873 小时前
【算法笔记】模拟与高精度加减乘除
c++·笔记·算法·高精度·模拟
鱼很腾apoc3 小时前
【学习篇】第17期 C++入门必看——类和对象全站最详篇
c语言·开发语言·学习·算法·青少年编程