算法:归并排序复习 | 递归与非递归写法——笔记自用

前言

在好久之前学习过排序算法,本篇文章用来复习归并排序,将实现递归和非递归版本。

递归写法

代码

void Copy(int* src, int* dest, int left, int right)
{
	while (left <= right)
	{
		dest[left] = src[left];
		left++;
	}
}
void Merge(int* src, int* dest, int left, int m, int right)
{
	int i = left, j = m + 1;
	int index = left;

	while (i <= m && j <= right)
	{
		dest[index++] = src[i] <= src[j] ? src[i++] : src[j++];
	}

	while (i <= m)
	{
		dest[index++] = src[i++];
	}
	while (j <= right)
	{
		dest[index++] = src[j++];
	}
}
void MergePass(int* src, int* dest, int left, int right)
{
	if (left < right)
	{
		int mid = left + (right - left) / 2;
		MergePass(src, dest, left, mid);
		MergePass(src, dest, mid + 1, right);

		Merge(src, dest, left, mid, right);
		Copy(dest, src, left, right);
	}
}
void MergeSort(int* nums, int numsSize)
{
	if (nums == nullptr || numsSize < 2) return;
	int* tmp = new int[numsSize];
	MergePass(nums, tmp, 0, numsSize - 1);
	delete[] tmp;
}

解析

Merge函数的参数有五个,有三个相当于下标,这三个下标把一组数据分成两组。然后在函数内循环时候比较大小,小的数据往前放。退出第一个循环时有两种情况,要么前半部分数据放完了,要么后半部分数据放完了,再加两个循环对其补充,保证所有元素被放入到dest内。

对于MergePass函数,首先会找出中间下标,再两个递归,参数分别给的是左下标到中间下标,中间下标到右下标。在递推过程中,直到左右两个指针相遇,意味着只剩一个数据时,开始回归,回归时执行Merge和Copy函数,使得数据在回归时变得有序。是 11有序,22有序,44有序,每次回归后有序都是比上一次多一倍,因为递推划分的时候也是一半一半划分。

示例

int main(void)
{
	int nums[] = { 52, 12, 78, 90, 34, 23, 100, 56, 45, 67, 89 };
	int numsSize = sizeof(nums) / sizeof(nums[0]);
	
	PrintNums(nums, numsSize);
	MergeSort(nums, numsSize);
	PrintNums(nums, numsSize);
	return 0;
}

在这里插入图片描述

非递归

在之前写过非递归的过程,在这里先把代码贴出来,然后再给出第二种非递归形式。

方法1

static void Merge(int* arr, int len, int gap)
{
	//申请额外空间,等长于原始数组
	int* brr = (int*)malloc(sizeof(int) * len);
	assert(brr != NULL);

	int i = 0; // 额外数组brr的下标

	//申请四个指针,用来表示合并两组的边界
	int low1 = 0;
	int high1 = low1 + gap - 1;
	int low2 = high1 + 1;
	int high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;


	while (low2 < len) // 当low2满足小于len,则low1和high1肯定满足条件
	{
		//比较两组数据
		while (low1 <= high1 && low2 <= high2)
		{
			if (arr[low1] <= arr[low2])
			{
				brr[i++] = arr[low1++];
			}
			else
			{
				brr[i++] = arr[low2++];
			}
		}

		//上面的while循环跳出
		//要么因为右边组数据比较完了,那就把
		//左边组剩下的数据也放下来
		while (low1 <= high1)
		{
			brr[i++] = arr[low1++];
		}
		//要么因为左边组数据比较完了,那就把
		//右边组剩下的数据也放下来
		while (low2 <= high2)
		{
			brr[i++] = arr[low2++];
		}

		//更新指针
		low1 = high2 + 1;
		high1 = low1 + gap - 1;
		low2 = high1 + 1;
		high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;
	}

	//上面的外层循环跳出,可能存在这种情况
	//即右边组没有数据了,左边还有,则把左边数据放下来
	while (low1 < len)
	{
		brr[i++] = arr[low1++];
	}

	//再把排好序后的数据放回原始数组
	for (int j = 0; j < len; ++j)
	{
		arr[j] = brr[j];
	}

	//再把额外空间释放掉
	free(brr);
	brr = NULL;
}

void Merge_Sort(int* arr, int len)
{
	assert(arr != NULL);

	for (int i = 1; i < len; i *= 2)
	{
		Merge(arr, len, i);
	}
}

方法2

和递归方法一样,先将Merge函数写出来。

void Merge(int* src, int* dest, int left, int m, int right)
{
	int i = left, j = m + 1;
	int index = left;

	while (i <= m && j <= right)
	{
		dest[index++] = src[i] <= src[j] ? src[i++] : src[j++];
	}

	while (i <= m)
	{
		dest[index++] = src[i++];
	}
	while (j <= right)
	{
		dest[index++] = src[j++];
	}
}
void MergePass(int* src, int* dest, int n, int s)
{
	int i = 0;
	for (i = 0; i + 2 * s - 1 <= n - 1; i = i + 2 * s)
	{
		Merge(src, dest, i, i + s - 1, i + 2 * s - 1);
	}

	if (n - 1 > i + s - 1)
	{
		Merge(src, dest, i, i + s - 1, n - 1);
	}
	else
	{
		for (int j = i; j < n; ++j)
		{
			dest[j] = src[j];
		}
	}
}

void MergeSort(int* nums, int n)
{
	if (nums == nullptr || n < 2) return;
	int* tmp = new int[n];
	int s = 1; // 块的元素个数

	while (s < n)
	{
		MergePass(nums, tmp, n, s);
		s += s;
		MergePass(tmp, nums, n, s);
		s += s;
	}
	delete[]tmp;
}

分析

对于MergePass函数,for循环每次迭代,i的增值是两个s块的大小减一。这个s是MergeSort函数传递进来的大小,表示本次调用MergePass函数就是按s进行划分的。MergeSort函数内的while循环,调用Pass后让s翻一倍,再调用Pass,使两个数组中元素来回倒换。Pass函数的for循环肯定有终止的时候,那么终止时,可能会出现这样的问题:划分后数组末尾仍然有数据,只是没有满足增量s那么多。所以需要将其考虑进去,判断还有数据时再调用Merge函数一次,如果不满足,那么就拷贝一次,将排序好的数组一次赋值给另一个数组中。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_索伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值