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

一.简述归并思想

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

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

时间复杂度: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就好了。因为拷贝范围不是整个数组,所以不存在不归并导致的随机值问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是归并排序的非归实现方式(C语言): ``` #include <stdio.h> #include <stdlib.h> void merge(int arr[], int left, int mid, int right) { int i, j, k; int n1 = mid - left + 1; int n2 = right - mid; int L[n1], R[n2]; for (i = 0; i < n1; i++) L[i] = arr[left + i]; for (j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; i = 0; j = 0; k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int n) { int currSize, leftStart; for (currSize = 1; currSize <= n - 1; currSize = 2 * currSize) { for (leftStart = 0; leftStart < n - 1; leftStart += 2 * currSize) { int mid = leftStart + currSize - 1; int rightEnd = fmin(leftStart + 2 * currSize - 1, n - 1); merge(arr, leftStart, mid, rightEnd); } } } void printArray(int arr[], int n) { int i; for (i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = {12, 11, 13, 5, 6, 7}; int n = sizeof(arr) / sizeof(arr[0]); printf("Given array is \n"); printArray(arr, n); mergeSort(arr, n); printf("\nSorted array is \n"); printArray(arr, n); return 0; } ``` 该程序中的`merge`函数可将两个已排序的子数组合并为一个已排序的子数组。`mergeSort`函数使用非方式实现归并排序。它将数组分成不同大小的子数组,并使用`merge`函数将它们合并为已排序的子数组。最后,`printArray`函数用于打印排序后的数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值