zl程序教程

您现在的位置是:首页 >  其他

当前栏目

小白也能看懂的归并排序!!!

2023-03-14 22:50:13 时间

1.前言

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

#include<iostream>
using namespace std;
#include<algorithm>
//合并两个有序数组
void MemeryArray(int a[],int la,int b[],int lb,int c[])
{
	int i=0, j=0, k=0;
	while (i < la && j < lb)
	{
		if (a[i] < b[j])
			c[k++] = a[i++];
		else
			c[k++] = b[j++];
	}
	while(i < la)
		c[k++] = a[i++];
	while (j < lb)
		c[k++] = b[j++];
}
void test()
{
	int a[] = { 1,5,8 };
	int b[] = { 2,3,6,10,12 };
	int c[8];
	MemeryArray(a, 3, b, 5,c);
	for_each(c, c + 8, [](int val) {cout << val << " "; });
}
int main()
{
	test();
	return 0;
}
  • 可以看出合并有序数列的效率是比较高的,可以达到 O(n)。
  • 解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组 A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
  • 可以将 A,B 组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

2.归并排序

一,概念及其介绍

  • 归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

二、适用说明

  • 当有 n 个记录时,需进行 logn 轮归并排序,每一轮归并,其比较次数不超过 n,元素移动次数都是 n,因此,归并排序的时间复杂度为 O(nlogn)。归并排序时需要和待排序记录个数相等的存储空间,所以空间复杂度为 O(n)。
  • 归并排序适用于数据量大,并且对稳定性有要求的场景。

三、过程图示

归并排序是递归算法的一个实例这个算法中基本的操作是合并两个已排序的数组,取两个输入数组 A 和 B,一个输出数组 C,以及三个计数器 i、j、k,它们初始位置置于对应数组的开始端。 A[i] 和 B[j] 中较小者拷贝到 C 中的下一个位置,相关计数器向前推荐一步。 当两个输入数组有一个用完时候,则将另外一个数组中剩余部分拷贝到 C 中。

  • 自顶向下的归并排序,递归分组图示:
  • 对第三行两个一组的数据进行归并排序
  • 对第二行四个一组的数据进行归并排序
  • 整体进行归并排序

3.归并排序递归代码

#include<iostream>
using namespace std;
#include<algorithm>
//合并两个有序序列
void mergearray(int a[], int first, int mid, int last, int temp[])
{
	int i =first, j = mid+1, k = 0;
	while (i <=mid && j <=last)
	{
		if (a[i] < a[j])
			temp[k++] = a[i++];
		else
			temp[k++] = a[j++];
	}
	while (i <=mid)
	{
		temp[k++] = a[i++];
	}
	while (j <=last)
	{
		temp[k++] = a[j++];
	}
	//最后把temp里面存放的合并后为有序的左右序列倒回原数组a
	//注意a的起点
	for (int i = 0; i < k; i++)
	{
		a[first+i] = temp[i];
	}
}
//拆分数组,有序合并
void mergesort(int a[],int first,int last,int temp[])
{
	//没有拆分到只剩一个元素的极小序列
	if(first < last)
	{
		int mid = (first + last) / 2;
		mergesort(a, first, mid, temp);//拆分左边序列,合并为有序序列
		mergesort(a, mid + 1, last, temp);//拆分右边序列,合并为有序序列
		mergearray(a, first, mid, last, temp);//将左序列和右序列合并为一整个有序序列
	}
}
//归并排序主函数
void MergeSort(int a[],int n)
{
	int* p = new int[n];
	if (!p)
		return;
	mergesort(a,0,n-1,p);
	delete[] p;
}
void test()
{
	int a[] = { 1,3,5,2,7,9,10 };
	MergeSort(a, 7);
	for_each(a, a + 7, [](int val) {cout << val << " "; });
}
int main()
{
	test();
	return 0;
}
  • 注:有的书上是在 mergearray() 合并有序数列时分配临时数组,但是过多的new操作会非常费时。因此作了下小小的变化。只在 MergeSort() 中new 一个临时数组。后面的操作都共用这一个临时数组。

4.递归排序的迭代

  • 下面这张图演示的是递归的过程
  • 而迭代的过程就是化成一个个小的排序问题,再合并到一起
  • 迭代的过程只有递归过程的一半,因此可以节约不少资源和空间,因此一般在用归并排序的时候会采用迭代的实现方式
#include<iostream>
using namespace std;
#include<algorithm>
//合并两个有序序列
void mergearray(int a[], int first, int mid, int last, int temp[])
{
	int i =first, j = mid+1, k = 0;
	while (i <=mid && j <=last)
	{
		if (a[i] < a[j])
			temp[k++] = a[i++];
		else
			temp[k++] = a[j++];
	}
	while (i<=mid)
	{
		temp[k++] = a[i++];
	}
	while (j <=last)
	{
		temp[k++] = a[j++];
	}
	//最后把temp里面存放的合并后为有序的左右序列倒回原数组a
	//注意a的起点
	for (int i = 0; i < k; i++)
	{
		a[first+i] = temp[i];
	}
}
//拆分数组,有序合并
void mergesort(int a[],int first,int last,int temp[])
{
	//迭代的实现是直接从最小子序列(含一个元素)开始往上两两合并
	int k = 1;//子序列长度
	int Last=0;
	int First=0;
	int mid=0;
	while (k<last)
	{
		First =0;
		//3.剩余大于等于两个子序列的元素个数
		while (First<=last-2*k+1)//加一的原因是因为下标从0算起
		{
			mid = First + k - 1;
		     Last = First + 2 * k - 1;
			mergearray(a, First, mid, Last, temp);
			First += 2 * k;
		}
		//当剩下的没有合并处理过的元素数量不足2k,即无法构成两个子序列进行合并操作时,要分类处理
	   //1.剩下小于等于一个子序列的元素个数
		if (last-First<=k)
		{
			mid = First + (last - First) / 2;
			mergearray(a, First, mid, last, temp);
		}
		//2.剩下大于一个小于两个的子序列元素个数
		else
		{
			mid = First + k - 1;;
			mergearray(a,First, mid, last, temp);
		}
		k *= 2;
	}
	//说明此时是特殊奇数情况,数组末尾还单着一个元素没有于前面一个完整的子序列合并
	if (First==last)
	{
		mergearray(a, first, last - 1, last, temp);
	}
}
//归并排序主函数
void MergeSort(int a[],int n)
{
	int* p = new int[n];
	if (!p)
		return;
	mergesort(a,0,n-1,p);
	delete[] p;
}
void test()
{
	int a[] = { 3,4,5,6,1,2,7,9,32,6,89,0,5,21,56,22,0,3,1};
	MergeSort(a,19);
	for_each(a, a+19, [](int val) {cout << val << " "; });
}
int main()
{
	test();
	return 0;
}

下面是画图分析子序列剩余的三种情况

(1)剩下小于等于一个子序列的元素个数

(2)剩下大于一个小于两个的子序列元素个数

(3)剩余大于等于两个子序列的元素个数

(4)特殊奇数情况,数组末尾还单着一个元素没有于前面一个完整的子序列合并

  • 当数组长度为17时,k至多为16,也会剩余一个元素,此时first==last,还需额外进行一次合并。当数组长度为33…等等,这种特殊奇数情况,大家可以好好思考一下

完结撒花!!!