【C语言】归并排序


在这里插入图片描述

一、什么是归并排序

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

在这里插入图片描述

二、归并排序步骤图解

算法思想:

归并排序算法有两个基本的操作,一个是分解,另一个是合并。分解是把原数组划分成两个子数组的过程,合并可以将两个有序数组合并成一个更大的有序数组。

将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

示例图解步骤:

对如图的一个数组进行归并排序的步骤
在这里插入图片描述

三、归并排序代码实现

使用归并排序对一个数组a排序,需要另外创建一个数组tmp,用来存放有序子序列再放回去,具体过程如下

归并过程图解:

在这里插入图片描述

1、递归实现

//归并排序
void _MergeSort(int* a, int* tmp, int begin, int end)
{
    
    
	//递归结束条件
	if (begin >= end)
	{
    
    
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, tmp, begin, mid);
	_MergeSort(a, tmp, mid+1, end);

	// 归并到tmp数据组,再拷贝回去
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int index = begin;
	//begin小于end说明还有两部分都还有数据
	while (begin1 <= end1 && begin2 <= end2)
	{
    
    
		if (a[begin1] < a[begin2])
		{
    
    
			tmp[index++] = a[begin1++];
		}
		else
		{
    
    
			tmp[index++] = a[begin2++];
		}
	}
	//由于右边没有数据跳出的上一个循环,将左边剩下的数放入tmp数组对应位置
	while (begin1 <= end1)
	{
    
    
		tmp[index++] = a[begin1++];
	}
	//由于左边没有数据跳出的上一个循环,将右边剩下的数放入tmp数组对应位置
	while (begin2 <= end2)
	{
    
    
		tmp[index++] = a[begin2++];
	}

	// 拷贝回原数组
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}



void MergeSort(int* a, int n)
{
    
    
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
    
    
		perror("malloc fail");
		return;
	}

	_MergeSort(a, tmp, 0, n - 1);

	free(tmp);
}

2、非递归实现

非递归实现时当gap的值不同时有许多数组的数据个数不适合当前gap,访问就会越界,比如9个值时当gap==1就会访问到下标为9的下标越界,所以要在代码中加入解决措施。当第一组右边界越界,第二组左边界也一定越界了,所以可分为第二组左边界越界和第二组右边界越界两种情况处理。

//非递归归并
void MergeSortNonR(int* a, int n)
{
    
    
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
    
    
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
    
    
		for (int i = 0; i < n; i += 2 * gap)
		{
    
    
			// 归并到tmp数据组,再拷贝回去
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;

			// 如果第二组不存在,这一组不用归并了
			if (begin2 >= n)
			{
    
    
				break;
			}

			// 如果第二组的右边界越界,修正一下
			if (end2 >= n)
			{
    
    
				end2 = n - 1;
			}

			int index = i;
			//begin小于end说明还有两部分都还有数据
			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] < a[begin2])
				{
    
    
					tmp[index++] = a[begin1++];
				}
				else
				{
    
    
					tmp[index++] = a[begin2++];
				}
			}
			//由于右边没有数据跳出的上一个循环,将左边剩下的数放入tmp数组对应位置
			while (begin1 <= end1)
			{
    
    
				tmp[index++] = a[begin1++];
			}
			//由于左边没有数据跳出的上一个循环,将右边剩下的数放入tmp数组对应位置
			while (begin2 <= end2)
			{
    
    
				tmp[index++] = a[begin2++];
			}

			// 拷贝回原数组
			memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
		}
		gap *= 2;
	}
	free(tmp);
}

四、总结

归并排序的算法需要另外创建很大的空间,用来存放排序好的子表,然后再放回原线性表中,所以归并排序的空间复杂度比较大。

  • 归并排序时间复杂度为:O(N*logN)
  • 归并排序空间复杂度为:O(N)
  • 归并排序是稳定的排序
  • 归并排序属于外排序,在磁盘中进行

猜你喜欢

转载自blog.csdn.net/zcxyywd/article/details/133491684