归并排序算法

文章目录

  • 归并排序的基本思想及递归实现
  • 二、归并排序的非递归实现

一、归并排序的基本思想

归并排序算法是用分治策略实现对n个元素进行排序地算法。其基本思想是:将待排序元素分为大致相等的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并为要求的集合。

12182334452548677885
0123456789

如图,我们用left,mid,right分别表示数组第一个,中间的以及最后一个元素,数组从left到mid从小到大有序,mid+1到right从小到大有序,我们只需把其合并到另一个缓冲区中从小到大有序就完成了。思路和实现非常简单,代码如下:


void Merge(int* dest, int* src, int left, int mid, int right)
{
	int i = left, j = mid + 1;
	int k = left;
	while (i <= mid && j <= right)
	{
		dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
	}
	while (i <= mid)
	{
		dest[k++] = src[i++];
	}
	while (j <= right)
	{
		dest[k++] = src[j++];
	}
}

那么问题来了,如果子集合中数组无序怎么办呢?这时就要用到分治策略了。

56237845908912348967100
0123

4

5678910

如图,我们发现分割后的数组依然无序,满足不了合并的条件,那好我们按照上面方法接着分,不断减小问题规模,最坏情况无非是分成一堆就剩一个元素的数组,那你总该有序了,这时我们在合并就完事了。代码实现如下:


void Merge(int* dest, int* src, int left, int mid, int right)
{
	int i = left, j = mid + 1;
	int k = left;
	while (i <= mid && j <= right)
	{
		dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
	}
	while (i <= mid)
	{
		dest[k++] = src[i++];
	}
	while (j <= right)
	{
		dest[k++] = src[j++];
	}
}

void Copy(int* dest, int* src, int left, int right)
{
	for (int i = left; i <= right; ++i)
	{
		dest[i] = src[i];
	}
}

void MergePass(int* tmp, int* nums, int left, int right)
{
	if (left < right)
	{
		int mid = (left + right) / 2;
		MergePass(tmp, nums, left, mid);
		MergePass(tmp, nums, mid + 1, right);
		Merge(tmp, nums, left, mid, right);
		Copy(nums, tmp, left, right);
	}
}

void MergeSort(int* nums, int n)
{
	if (nullptr == nums || n < 2) return;
	int* tmp = new int[n];
	MergePass(tmp, nums, 0, n - 1);
	delete []tmp;
}

int main()
{
	int ar[] = { 56,23,78,45,90,89,12,34,89,67,100 };
	int n = sizeof(ar) / sizeof(ar[0]);
	MergeSort(ar, n);
	for (auto x : ar)
	{
		cout << x << "  ";
	}
	cout << endl;
	return 0;
}

运行结果:

 二、归并排序的非递归实现

通过上一篇快速排序的博客我们知道(快速排序算法_阿强O.o的博客-CSDN博客),非递归快排我们是借助栈或队列来实现的,那么这种方法是否适用于这里的非递归归并呢?很明显是不行的,因为我们存储是先存储0到n-1,然后再存储left到mid,mid+1到right,和快排不同,这里数组不一定有序,不能直接取出,需要不断进行存储,实现起来太麻烦。

s=1
56237845908912348967100
012345678910

s为我们每次需要合并的个数,当s=1时,我们调动合并函数void Merge(int* dest, int* src, int left, int mid, int right)。第一次传入参数left=0,mid=0,right=1,第二次传入参数left=2,mid=2,right=3,第三次传入参数left=4,mid=4,right=5,第四次传入参数left=6,mid=6,right=7,第五次传入参数left=8,mid=8,right=9。可以看出,10号数据没有元素与其进行合并,这是因为我们元素的个数并不是2的幂次方,最后只需将其拷贝下来就行。然后让s+=s,直到s大于数组长度,说明数组已排序完成。

void MergeSort(int* nums, int n)
{
	int* tmp = new int[n];
	int s = 1;
	while (s < n)
	{
		MergePass(tmp, nums, n, s);
		s += s;
		MergePass(nums, tmp, n, s);
		s += s;
	}
	delete[]tmp;
}
s=2
56237845908912348967100
012345678910

以s=2为例,s为每一次合并块的大小,所以i每次走的都是2倍的s的长度,每次调用合并函数(即上面的Merge函数)传入的mid值即为i+s-1,right为i+2*s-1。当我们最后一次调用Merge函数时,理想中的right为8+2*2-1=11,超出了数组长度,故退出。

再接着,最后一次合并,我们并不能确定是像s=1那样没有数据与剩余元素合并,还是像s=2一样,是两个数据与一个数据进行合并。所以我们需要进行判断,数组末尾元素是否超出一个合并块的大小(即一个s),如果没有超出,我们只需拷贝即可,如果超出了,那么我们还要在进行一次合并。

void MergePass(int* dest, int* src, int n, int s)
{
	int i = 0;
	while (i + 2 * s - 1 <= n - 1)
	{
		Merge(dest, src, i, i + s - 1, i + 2 * s - 1);
		i = i + 2 * s;
	}
	if (n - 1 >= i + s)
	{
		Merge(dest, src, i, i + s - 1, n - 1);
	}
	else
	{
		for (int j = i; j < n; ++j)
		{
			dest[j] = src[j];
		}
	}
}
s=4
56237845908912348967100
012345678910

s=8
56237845908912348967100
012345678910

最终代码如下:

void Merge(int* dest, int* src, int left, int mid, int right)
{
	int i = left, j = mid + 1;
	int k = left;
	while (i <= mid && j <= right)
	{
		dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
	}
	while (i <= mid)
	{
		dest[k++] = src[i++];
	}
	while (j <= right)
	{
		dest[k++] = src[j++];
	}
}

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

void MergeSort(int* nums, int n)
{
	int* tmp = new int[n];
	int s = 1;
	while (s < n)
	{
		MergePass(tmp, nums, n, s);
		s += s;
		MergePass(nums, tmp, n, s);
		s += s;
	}
	delete[]tmp;
}

感谢观看!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值