归并排序有多简单?一幅图教你看懂【C语言】

举报
花想云 发表于 2023/05/31 22:58:03 2023/05/31
【摘要】 归并排序的递归实现代码实现归并排序的非递归实现代码实现归并排序的思想很简单——分治法。简单地说,归并排序的是将序列拆分成几段子序列,将每一段子序列分别进行排序,排好之后再将有序的子序列归并(有点像合并两个有序数组)成为一个有序的序列。

 编辑

目录

归并排序的递归实现 

代码实现

归并排序的非递归实现 

代码实现


归并排序的思想很简单——分治法。简单地说,归并排序的是将序列拆分成几段子序列,将每一段子序列分别进行排序,排好之后再将有序的子序列归并(有点像合并两个有序数组)成为一个有序的序列。

例如要排序数列:10、6、7、1、3、9、4、2;

将序列拆分为 2 个子序列;


继续拆分;

 

 继续拆分;


至此,每个子序列的长度都为 1 ,因为只有一个数,所以可认为是有序序列;

现将子序列两两归并,即合并两个有序序列;

继续归并;

编辑继续归并; 

编辑以上就是归并排序的整个过程,很显然归并排序的实现应该离不开递归的思想。


归并排序的递归实现 

归并排序的递归实现较为简单,需要注意的有两点:

1. 归并的过程并非在原数组上直接改动,而是开辟一个临时数组,在临时数组上进行排序,排好之后将临时数组的内容全部拷贝到原数组;

2. 代码中使用的是二路归并(如上图所示,每次将序列拆分为两个子序列)。

代码实现

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	//递归的结束条件//当序列只有一个元素时或序列不存在时
	if (begin >= end)
		return;

	//将序列进行拆分 //[begin,mid]  [mid+1,end]
	int mid = (begin + end) / 2;

	//拆分的过程
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);
	
	//以下为归并的过程
	int begin1 = begin, end1 = mid;
	int begin2 = mid+1, end2 = end;
	int i = begin;
	//归并:合并两个有序序列
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//如果第二段序列先结束
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	//如果第一段序列先结束
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//将临时数组的数据拷贝回原数组
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a,int n)
{
	//开辟一个临时数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

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

	//释放与置空
	free(tmp);
	tmp = NULL;
}

归并排序的非递归实现 

非递归与递归作用思想基本相同。递归实现时,因为拆分序列时采用的是递归的方式,所以通过传递参数就可以控制子序列的长度。但是非递归不行,非递归通过变量 rangeN 来控制序列的长度(或间隔),每次让 rangeN *= 2 例如:


但是由于 rangeN 每次都 *2 ,而我们排序的序列长度不可能总是 2 的倍数,所以 可能会有数组越界访问的风险。例如:

现将两个子序列归并,并将数据拷贝回原数组时,就会发生越界:

编辑当然这只是其中一种越界的可能情况——第二段序列发生越界,原因是右边界 end2 大于 n;

实际操作中,一共会有三种情况导致越界:

两段序列的区间分别为: [begin1,end1]  [begin2,end2]

1. end1 > n;

2. begin > n;

3.end2 > n;

所以,当这三种情况发生时,需要修正区间,以上述用例为例, end2 大于 n 时,令 end2 = n-1即可;

代码实现

void MergeSortNonR(int* a, int n)
{
	//开辟一个临时数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int rangeN = 1;

	while (rangeN < n)
	{
        // i 控制访问子序列的位置
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			//拆分为两段子序列//[begin1,end1] [begin2,end2]
			int begin1 = i, end1 = i + rangeN - 1;
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;

			int j = i;
            
            //判断是否发生越界的三种情况,如果有,就修正区间
			if (end1 >= n)
			{
				end1 = n - 1;
				//将第二段序列改为不存在的序列即可
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				//将第二段序列改为不存在的序列即可
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
                //修正区间
				end2 = n-1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			//如果第二段序列先结束
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			//如果第一段序列先结束
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
		}
		//将临时数组的内容拷贝回原数组
		memcpy(a, tmp, sizeof(int) * n);

		//控制间隔
		rangeN *= 2;
	}

	//释放与置空
	free(tmp);
	tmp = NULL;
}


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。