排序——归并排序

归并排序和快排一样, 都是一种利用二叉树分治思想实现的排序。同时归并排序也和快排一样有递归归并排序和非递归归并排序两种。 本节主要复习归并排序, 并且两种实现方式都会复习到。

递归归并

要实现递归归并排序的代码。 我们首先需要理解递归归并排序的思想。 递归归并的过程如下:

假如有一个数组

int a[10] = { 9, 0, 7, 5, 4, 3, 1, 2, 8, 6 };
int sz = sizeof(a) / sizeof(a[0]);

 要对这个数组进行排序。 

那么利用归并的分治思想, 就是先将数据进行如图划分。

图中每个子节点都标识范围内的数据。 比如9 0 7就表示0倒2这个区间内的三个数据。然后进行后序归并。 

 代码实现如下

首先创建一个子函数, 方便递归, _MergeSort就是我们要实现的归并排序过程



void MergeSort(int* a, int sz) 
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	//
	_MergeSort(a, 0, sz - 1, tmp);

	free(tmp);
}

//归并排序递归版本
void _MergeSort(int* a, int left, int right, int* tmp) 
{
	if (left >= right)//如果区间不存在或者只剩下一个节点, 直接返回。 
	{
		return;
	}
	//
	int mid = (left + right) / 2;//让区间中分
	_MergeSort(a, left, mid, tmp);//从左边开始归并。 两两一组进行归并。
	_MergeSort(a, mid + 1, right, tmp);//两俩个一组
    
    //后续归并。所以先递归再进行归并

	int begin1 = left;//对中分点的左右两个区间进行归并。 begin1 为第一个区间左边
	int end1 = mid;//end1为左区间右边。
	int begin2 = mid + 1;//begin2为右区间左边
	int end2 = right;//end2为右区间右边
	int j = left;//让j从左区间左边开始控制, j是控制tmp拷贝数组的拷贝。 在一块区间进行归并, 就应该将这块区间的数据拷贝到tmp的对应区间上

	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 + left, tmp + left, sizeof(int) * (right - left + 1));

}

非递归归并

进行非递归归并, 归并的过程也是一种分治, 不过利用了一个gap值控制每次归并的分治区间大小。直接上代码


//归并非递归
void MergeSortRno(int* a, int sz) 
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	//
	int gap = 1;
	while (gap < sz) 
	{
		
		gap *= 2;
	}
}

着段代码的意思是, 从区间为1开始, 归并之后。 区间乘以二。然后再进行归并。 然后再乘以二, 再进行归并。 最后一次归并可能很巧合gap就是sz / 2,这个时候不需要进行其他操作。 但是如果最后一次归并gap 比 sz / 2大的话。 那么就需要额外的操作。 这个问题会放到后面说, 这里先不讨论。 先将归并的内容实现。

//归并非递归
void MergeSortRno(int* a, int sz) 
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	//
	int gap = 1;
	while (gap < sz) 
	{
		for (int i = 0; i < sz; i += 2 * gap) 
		{
            
		}

		gap *= 2;
	}
}

归并的时候每gap是一个区间, 每两个区间进行归并。 当gap很小的时候, 很显然这个数组一定不是只有一两个区间。 这些区间进行归并的时候, 应该将所有的区间两两归并。 从下标为0, 向后依次进行。 

这里for循环就是来完成这个操作的,如图为gap为1的时候进行的归并, 每两个区间进行归并,一共五组, 从左到右依次进行:

然后具体的递归过程就和递归归并的归并过程大概是一样的了。

//归并非递归
void MergeSortRno(int* a, int sz) 
{
	int* tmp = (int*)malloc(sizeof(int) * sz);
	//
	int gap = 1;
	while (gap < sz) 
	{
		for (int i = 0; i < sz; i += 2 * gap) 
		{

            //归并过程
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			
			if (end1 > sz || begin2 > sz) 
			{
				break;
			}
			if (end2 > sz) 
			{
				end2 = sz - 1;
			}

			int j = i;
			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 + i, tmp + i, sizeof(int) * (end2 - i + 1));//请注意这里



		}

		gap *= 2;
	}
}

这里之所以说是大概, 就是因为这里有个超出范围的坑。 我们进行非递归归并的过程过程中可能遇到这种情况。

就比如我们这个十个大小的数组,当gap为2的时候范围就出问题了。如图:

当gap为二的时候, 第一组进行归并的是0-1区间和2-3区间归并。 第二组区间分别是4-5和6-7归并。 第三组本来应该是8-9和10-11, 但是10-11不在数组里面。我们是没有访问权限的。

所以这就出问题了。 那么解决问题的方式就是对着种越界情况进行判断。

我们分析这种越界情况有几种?

第一种:第一个区间的右区间超出范围, 这种显然不成立。 

第二种:第二个区间的左区间超出范围, 当第一个区间的右区间超出时这种就一定成立。

第三种:第二个区间的左区间没有超范围, 也就是第一种和第二种情况都不符合的情况下右区间超出了范围。 

那么解决方法就是这几行代码:

	if (end1 > sz || begin2 > sz) 
	{
		break;
	}
	if (end2 > sz) 
	{
		end2 = sz - 1;
	}

首先第一个if的意思就是第一种情况或者第二种情况。 这个时候break就行了。 最后一组的第二个区间不存在, 不需要进行归并, 直接跳出循环就行了。 

第二个if就是在第一种情况合第二种情况都不符合的情况下可能符合。 这个时候最后一组的第二个区间内有数据, 但是和其他的区间数据个数不同, 虽然不同,但它也是可以进行归并的。 所以我们只要修改右区间的范围就行了。

  • 31
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值