C语言实现归并排序(Merge Sort)

目录

一、递归实现归并排序

[1. 归并排序的基本步骤](#1. 归并排序的基本步骤)

2.动图演示

3.基本思路

4.代码

二、非递归实现

1.部分代码

2.代码分析

修正后代码:

归并过程打印

性能分析

复杂度分析


归并排序是一种高效的排序算法,采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序的基本思想是将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

一、递归实现归并排序

1. 归并排序的基本步骤

  1. 分解:将数组分解成两个较小的子数组,直到子数组的大小为1。
  2. 递归进行排序并合并:递归地对子数组进行排序,并将已排序的子数组合并成一个大的有序数组,直到合并为1个完整的数组。

2.动图演示

3.基本思路

归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。

那么如何得到有序的子序列呢?当序列分解到只有一个元素或是没有元素时,就可以认为是有序了,这时分解就结束了,开始合并。

4.代码

void _MergSort(int* a, int left,int right,int* tmp)
{
	if (left >= right)
		return;
	int mid = left + (right - left) / 2;

	_MergSort(a, left, mid, tmp);
	_MergSort(a, mid+1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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++];
	}
	for (int j = 0; j <= right; j++)
	{
		a[j] = tmp[j];
	}
}

void MergSort(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	_MergSort(a,0, n-1, tmp);
	free(tmp);
}

二、非递归实现

归并排序的非递归算法并不需要借助栈来完成,我们只需要控制每次参与合并的元素个数即可,最终便能使序列变为有序:

1.部分代码

void MergSortNonF(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			//*****注意边界处理*******
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + gap * 2 - 1;
			printf("[%d %d] [%d %d]", begin1, end1, begin2, end2);
			int i = j;
			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++];
			}

			
		}
		printf("\n");
		memcpy(a, tmp, n * sizeof(int));
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

2.代码分析

这是一个有问题的代码,当我们存放的数据是2的次方时,可以正常排序,为了便于观察,我们把它的下标给打印下来

如果我们存放的数据不是2的次方个就会出现一些越界。

情况一:第二组部分越界

当最后一个小组进行合并时,第二个小区间存在,但是该区间元素个数不够gap个,这时我们需要在合并序列时,对第二个小区间的边界进行控制。

情况二:第二组全部越界

当最后一个小组进行合并时,第二个小区间不存在,此时便不需要对该小组进行合并。

情况三:第一组end1越界

当最后一个小组进行合并时,第二个小区间不存在,并且第一个小区间的元素个数不够gap个,此时也不需要对该小组进行合并。(可与情况二归为一类)

只要把控好这三种特殊情况,写出归并排序的非递归算法便轻而易举了。

修正后代码:

void MergSortNonF(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			//*****注意边界处理*******
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + gap * 2 - 1;
			
			//第一组越界
			if (end1 >= n)
			{
				printf("[%d %d]", begin1,n-1);
				break;
			}

			//第二组全部越界
			if (begin2 >= n)
			{
				printf("[%d %d]", begin1, end1);
				break;
			}

			//第二组部分越界,越界之前的那一部分依然要归
			if (end2 >= n)
			{
				//修正end2
				end2 = n - 1;
			}

			printf("[%d %d] [%d %d]", begin1, end1, begin2, end2);

			int i = j;
			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+j, tmp+j, (end2-j+1)* sizeof(int));
		}
		printf("\n");
		//memcpy(a, tmp, n * sizeof(int));
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

归并过程打印

性能分析

复杂度分析

  • 时间复杂度:归并排序的时间复杂度为O(n log n),其中n是数组的长度。这主要是由于归并排序将问题分解为两个子问题(分解),递归地解决它们(递归),然后将解决方案合并(合并)。
  • 空间复杂度:归并排序的空间复杂度为O(n),其中n是数组的长度。这是因为归并排序在合并过程中需要与原数组同样大小的额外空间来存放临时数组。

归并排序是稳定的排序算法,即相等的元素在排序后的序列中仍然保持原来的顺序。这个特性在某些应用中非常重要。


如有错误,劳烦各位指正

相关推荐
羊小猪~~1 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
软工菜鸡27 分钟前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生29 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
AI视觉网奇1 小时前
sklearn 安装使用笔记
人工智能·算法·sklearn
JingHongB1 小时前
代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础
算法·深度优先·图论
weixin_432702261 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
小冉在学习1 小时前
day52 图论章节刷题Part04(110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长 )
算法·深度优先·图论
Repeat7151 小时前
图论基础--孤岛系列
算法·深度优先·广度优先·图论基础
小冉在学习1 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论
武子康2 小时前
大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和
大数据·人工智能·学习·算法·机器学习·数据挖掘