【归并排序的简要理解】递归及非递归方式

一.简述归并思想

归并本质上使用了分治的思想,有点像二叉树的后续遍历,同时归并排序是一种很典型的外部排序。非常适合解决磁盘中的排序问题。

给一个待排序的数组,不断将其划分直至只含有一个元素,最后将两个元素按数序比较结合起来。然后两两结合,然后四个四个一结合。

时间复杂度:O(n*logn)  每层消耗n 一共有log n层.

空间复杂度:O (N) 因为要单独开辟一个和原数组大小相等的数组。

稳定性 :稳定 (是否稳定取决与代码,只要在遇到相同的数的时候不交换位置即可)

 图片来自 菜鸟教程

二 代码实现 递归版

void Mergesort(int* arr, int left, int right, int* tmp)//递归实现
{
	if (left>=right){                           //处理递归的返回条件
		return;
	}
	int mid = (left + right+1) / 2;
	Mergesort(arr,left,mid-1,tmp);                  //递归本质是二叉树的后序
	Mergesort(arr,mid,right,tmp);
	int begin1 = left, end1 = mid-1;
	int begin2 = mid, end2 = right;
	int i = begin1;
	while (begin1 <= end1 && begin2 <= end2)      //从这里开始处理排序,排序后放到临时数组里,最后拷进原数组
	{
		if (arr[begin1] <= arr[begin2])     //因为begin1开始的整体是比begin2开始的整体靠左的,在遇到相等时,为保证稳定性让左边的先落下
		{
			tmp[i++] = arr[begin1++];
		}
		else
		{
			tmp[i++] = arr[begin2++];
		}
	}
	while(begin1<=end1)                   // 当划分的两端,一侧走完另一侧没走完时 直接将没走完的剩余数字拷进临时数组
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	memcpy(arr + left, tmp + left, (right - left + 1) * sizeof(int)); //最后将临时数组拷贝到原始数组,并继续向下递归
}

三 代码实现(非递归)

递归相比非递归比较难以实现,且非递归无法用栈或者队列进行模拟,因为栈或者队列实现的递归方式划分后的区域只能取到一次,也就是说只能划分。但归并排序还需要一个归并回去的过程。这个时候假如用栈或者队列的话,栈内已经空了。

void Mergesort2(int* arr, int len, int* tmp)//非递归实现
{
	
	int gap = 1;
	while (gap < len)
	{
		for (int i = 0; i < len; i+=2*gap)              //处理边界,原情况下不是2的倍数的都会溢出,因为边界是手动算出来的,存在
		{												//不存在的情况
			int begin1 = i, end1 = i + gap - 1;          //[i,i+gap-1][i+gap,i+2*gap-1]
			int begin2 = i + gap, end2 = i + 2 * gap - 1;//注意不能跳出,就算范围不存在也要归并,因为拷贝的数组包含了这个位置,假如不包括的话,越界的位置就会变成随机值
			
			if (end1 >= len)   //当 end1 越界的时候 后序范围全部越界,可以修正成不存在的数组
			{
				end1 = len - 1;
				begin2 = len;
				end2 = len - 1;
			}
			if (end2 >= len) //当 end2 越界的时候 直接修正成数组最后的位置即可
			{
				end2 = len - 1;
			}
			if (begin2>=len)//同上
			{
				begin2 = len;
				end2 = len - 1;
			}
			int i = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] <= arr[begin2])
				{
					tmp[i++] = arr[begin1++];
				}
				else
				{
					tmp[i++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[i++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[i++] = arr[begin2++];
			}
			
		}
		memcpy(arr, tmp , len * sizeof(int));//拷贝的时候是原数组的整个范围,也可以放进归并过程心中
		gap*=2;
	}
}

我们可以手动实现归并过程,因为归并的范围是可以手动算出来的。【i,gap-1】是一个范围,【gap,i+gap*2-1】是另一个范围。但这里存在一个可能越界的问题.理论上 除了begin1,也就是i,其他边界都有可能越界,这时候就要对越界的进行修正。

从每次归并一个开始,下次每次归两个,依次乘以2。这里用gap表示每次归并的范围。归并的过程和递归的归并过程完全一致,这里我选择了归并完相同的gap后再进行拷贝,也可以归并一次拷贝一次。

但假如说归并一次一次拷贝的话有两个需要注意的点。一是拷贝的范围和大小,有可能不是从最开始拷贝的所以要加一个计数位置。同时拷贝的大小也不是原数组整个大小,而是从begin1到end2的范围。另一个注意点是边界修正问题,这次在end1或者begin2越界的时候就可以不归并了,在end2越界的时候直接修正end2就好了。因为拷贝范围不是整个数组,所以不存在不归并导致的随机值问题

你可能感兴趣的:(数据结构,排序算法)