归并排序详解(非递归)

感谢各位大佬的光临,希望和大家一起进步,望得到你的三连,互三支持,一起进步

个人主页:LaNzikinh-CSDN博客

收入专栏:http://t.csdnimg.cn/LJ2J2

文章目录

  • 前言
  • 一.归并排序
  • 二.代码实现归并排序(递归)
  • 三.代码实现归并排序(非递归)
  • 总结

前言

我们之前讲了希尔排序直接插入排序堆排序,我们今天来讲一讲归并排序,讲之前我们先要知道归并排序的思想和什么是归并排序


一.归并排序

什么是归并排序

归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。

归并排序的思想

规定排序的主要思想就是先分解再合并,先分解到只有一个的时候再两两合并,那他是怎么个排序法呢?两两归并,两组的头一个进行比较小的放在前面,然后往后加加,继续比较小的放前面按顺序排好。

二.代码实现归并排序(递归)

我需要在外面提前开辟一个空间,我为什么不在函数内部开辟,是因为函数递归需要反复调用函数,这样子会多次开辟空间,所以我在外面提前开辟了,再去释放

cpp 复制代码
void MergrSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergrSort(a, 0, n - 1,tmp);
	free(tmp);
}

2.2利用分治思路,将归并展开

我们将这个归并排序的一行数字的下标,分成两个区间,两个区间又继续分,有点像我们二叉树那里的左子树右子树的分治思路,我们可以根据之前那幅图分析我们最开始就是要先分开,然后在后面再进行归并

cpp 复制代码
void _MergrSort(int* a, int left, int right,int*tmp)
{
	if (left >= right)
		return;
	int mid = (left + right)/2;
	//用递归将[left,mid][mid+1,right]有序
	_MergrSort(a, left, mid,tmp);
	_MergrSort(a, mid+1, right, tmp);
	//归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = left;

2.3归并

根据上面的地规可以看出第一个循环就是第一组数已经分到最当头了,判定条件也很简单,只要左边不会超越右边就可以两边之中,只要有一边达到的话就要结束这个循环,后面的循环就是说,当一个区间结束时,把另一个区间拷回去

cpp 复制代码
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//当一个区间结束时,另一个区间要拷回去
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin1 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//最后把排好的数据拷入开辟的数组中去
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}	
}

2.4总代码

它其实整体的思路并不是像图中层序一样的往下走的,他其实是先把左边的排好了之后再一步一步往右边走的

cpp 复制代码
void _MergrSort(int* a, int left, int right,int*tmp)
{
	if (left >= right)
		return;
	int mid = (left + right)/2;
	//用递归将[left,mid][mid+1,right]有序
	_MergrSort(a, left, mid,tmp);
	_MergrSort(a, mid+1, right, tmp);
	//归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//当一个区间结束时,另一个区间要拷回去
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin1 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//最后把排好的数据拷入开辟的数组中去
	for (int i = left; i <= right; i++)
	{
		a[i] = tmp[i];
	}	
}
void MergrSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergrSort(a, 0, n - 1,tmp);
	free(tmp);
}

三.代码实现归并排序(非递归)

递归他就是把这一串数字先从左开始变得有序,再不断地向右延伸变得有序,最后再让整体变得有序,非递归就是不用递归的方式去想办法,那我们该怎么做呢?

思路就在这个图中

我们可以把这个数组进行分组,再把每一组进行归并,先从gap=1开始归并完之后,从gap=2开始一直往下,直到gap的值大于总长度就可以停止了
i += 2 * gap是控制gap为几的分组进行总体归并,就是i += 2 * gap了之后,可以要这组全部归并下去

[i,i+gap-1][i+gap,i+2*gap-1]是怎么看出来的,注意一点,这个是数组,[]内的都是下标,我是不是每次归并都需要找两个组去归,所以说我这个代码就是要找两个组的,而是初始位置i加上gap就是表示第一组的末尾,因为是下标,所以要减1,所以第2组的尾就是i+2*gap-1,然后这两组比完后面还会有,所以i += 2 * gap就可以比到后面的组了

cpp 复制代码
void MergeSortNonR(int* a, int n)
{
	//开辟空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	//gap从1开始,gap代表每组的数据个数
	int gap = 1;
	//gap大于总长度就停下来
	while (gap < n)
	{
		//i += 2 * gap控制gap为几的分组进行总体归并
		for (int i = 0; i < n; i += 2 * gap)
		{
			//[i,i+gap-1][i+gap,i+2*gap-1]
			int begin1 = i, end1 = i+gap-1;
			int begin2 = i + gap, end2 = i+ 2 * gap-1;
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//当一个区间结束时,另一个区间要拷回去
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin1 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			for (int j = 1; j <= n; j++)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

但是这个代码存在问题!!!

如果是这样就存在越界现象

改:

cpp 复制代码
//归并过程中右半区间可能不存在
if (begin2 >= n)
	break;
//归并过程中右半区间算多了,需要修正
if (end2 >= n)
{
	end2 = n - 1;
}


//循环这里也要改
for (int j = 1; j <= end2; j++)
{
	a[j] = tmp[j];
}

总代码

cpp 复制代码
void MergeSortNonR(int* a, int n)
{
	//开辟空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	//gap从1开始,gap代表每组的数据个数
	int gap = 1;
	//gap大于总长度就停下来
	while (gap < n)
	{
		//i += 2 * gap控制gap为几的分组进行总体归并
		for (int i = 0; i < n; i += 2 * gap)
		{
			//[i,i+gap-1][i+gap,i+2*gap-1]
			int begin1 = i, end1 = i+gap-1;
			int begin2 = i + gap, end2 = i+ 2 * gap-1;
			//归并过程中右半区间可能不存在
			if (begin2 >= n)
				break;
			//归并过程中右半区间算多了,需要修正
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			//当一个区间结束时,另一个区间要拷回去
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin1 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			for (int j = 1; j <= end2; j++)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}
	free(tmp);
}

总结

归并排序是算法中非常重要的一个方法,无论是递归还是非递归,我们都应该要搞清楚

相关推荐
代码雕刻家16 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain16 分钟前
算法 | 位运算(哈希思想)
算法
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
我是哈哈hh4 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
Tisfy4 小时前
LeetCode 2187.完成旅途的最少时间:二分查找
算法·leetcode·二分查找·题解·二分
Mephisto.java4 小时前
【力扣 | SQL题 | 每日四题】力扣2082, 2084, 2072, 2112, 180
sql·算法·leetcode
robin_suli4 小时前
滑动窗口->dd爱框框
算法
丶Darling.4 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5204 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法