数据结构初阶——排序(归并+计数)(Part4)

目录

一、归并排序(递归版)(时间:O(N*log(N)))

1、思路

 2、代码

二、归并排序(非递归版)

1、总体思路

2、每趟归并完再整体拷回去(梭哈,但不推荐)

(1)思路

(2)代码

3、每趟每归并一次就拷一次回去

(1)思路

(2)代码

三、计数排序(时间:O(N))

1、思路

2、使用条件

3、代码

一、归并排序(递归版)(时间:O(N*log(N)))

1、思路

在学C语言的时候,我们都知道把两个数组归并成一个数组的方法:就是先开一个 tmp 数组;然后给每个数组一个指针去维护;然后比较两个指针指向的值,把小的那个拿下来放到 tmp 数组;当全部元素都放到 tmp 数组后,最后把 tmp 数组的数据都拷回原数组中。

但是,上述归并排序的条件十分苛刻——只有当两个数组都是升序或降序时,才能用归并排序。

因此,归并排序的思路就出来了。对于一个数组,我们可以以中间为界,看成两个无序的数组。而要使用归并排序的前提是要这两个数组有序,因此只有让这两个区间的数组都有序,才能对这两个数组归并。因此,它的归并展开图是这样的:

 2、代码

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 curL = left, beginL = left, endL = mid;
	int curR = mid + 1, beginR = mid + 1, endR = right;
	int i = left;

	while ((curL <= endL) && (curR <= endR))
	{
		if (a[curL] < a[curR])
		{
			tmp[i++] = a[curL++];
		}
		else
		{
			tmp[i++] = a[curR++];
		}
	}

	while (curL <= endL)
	{
		tmp[i++] = a[curL++];
	}

	while (curR <= endR)
	{
		tmp[i++] = a[curR++];
	}

	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)calloc(n, sizeof(int));
	assert(tmp != NULL);

	_MergeSort(a, 0, n - 1, tmp); /* 由于递归时每个栈帧储存的是排序区间的下标,因此传下标就可以了,不用每次都开一个新数组 */

	free(tmp);

}

二、归并排序(非递归版)

1、总体思路

从上述递归版的归并排序中可以看出,我们是先让两段数组有序,然后再对这两段数组做动作。因此与二叉树的后序遍历相似。而且,递归时都是递归到最底层(即当数组长度为1时)才返回。

所以在非递归版的实现时,我们可以直接从递归的最底层开始模拟,即一一归并;然后再两两归并;然后再四四归并,最后再整体归并。

2、每趟归并完再整体拷回去(梭哈,但不推荐)

(1)思路

对于单趟来说,如果我们选择先把整个数组进行归并,最后再整体拷回原数组的话,那么就会遇到这三种情况:

第一种:右区间部分越界 

对于这种情况,我们可以直接调整 e2 的位置,然后把处于数组内的两部分进行归并。

第二种:左区间没越界,右区间完全越界

 对于这种情况,就不应该对右区间进行归并了,应该直接把左区间拷贝到 tmp 数组。最后再把 tmp 数组的数据拷回原数组。

那么,怎么才能使右区间不归并呢?很简单,直接把右区间改成一个不存在的区间就好了,这样它就不会进入拷贝的部分了。

第三种:左区间部分越界,右区间完全越界

对于这种情况,我们应该只拷贝左边没越界的区间到 tmp 数组,最后再把 tmp 数组的数据拷回原数组中。

那么,怎么才能使右区间不归并呢?很简单,直接把右区间改成一个不存在的区间就好了,这样它就不会进入拷贝的部分了。

(2)代码

/* 单趟梭哈 */
void MergeSortNonR1(int* a, int n)
{
	int* tmp = (int*)calloc(n, sizeof(int));
	assert(tmp != NULL);

	for (int gap = 1; gap < n; gap *= 2)
	{
		/* 单趟归并 */
		for (int beginL = 0; beginL < n; beginL += 2 * gap)
		{
			int endL = beginL + gap - 1, curL = beginL;
			int beginR = beginL + gap, endR = beginR + gap - 1, curR = beginR;
			int i = beginL; /* 维护tmp */

			/* 调整拷贝范围 */
			if ((endL >= n) || (beginR >= n)) /* 右半部分不归并 */
			{
				endL = n - 1;
				beginR = n;
				endR = n - 1;
			}
			else if (endR >= n) /* 归并没越界的部分 */
			{
				endR = n - 1;
			}

			//拷贝
			while ((curL <= endL) && (curR <= endR))
			{
				if (a[curL] < a[curR])
					tmp[i++] = a[curL++];
				else
					tmp[i++] = a[curR++];
			}

			while (curL <= endL)
				tmp[i++] = a[curL++];

			while (curR <= endR)
				tmp[i++] = a[curR++];
			
		}

		memcpy(a, tmp, sizeof(int) * n);
	}

	free(tmp);
}

3、每趟每归并一次就拷一次回去

(1)思路

对每趟归并排序来说,当两个区间都没越界时,每次对两个区间归并,然后把在 tmp 归并后的区间拷回原数组原来的位置。

但是,我们同样需要处理相关区间越界的问题。

第一种:左区间没越界,右区间部分越界

针对这种情况,我们要调整右区间右端的值,然后归并没越界的部分。

if (endR >= n)
    endR = n - 1;

第二种:左区间没越界,右区间完全越界

 对于这种情况,我们就可以不用对左区间进行归并了。

if (beginR >= n)
    break;

第三种:左区间部分越界,右区间完全越界

对于这种情况,与第二种的处理相同,即直接放弃归并。

if (beginR >= n)
    break;

(2)代码

//分步拷贝
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)calloc(n, sizeof(int));
	assert(tmp != NULL);

	for (int gap = 1; gap < n; gap *= 2)
	{
		/* 单趟归并 */
		for (int beginL = 0; beginL < n; beginL += 2 * gap)
		{
			int endL = beginL + gap - 1, curL = beginL;
			int beginR = beginL + gap, endR = beginR + gap - 1, curR = beginR;
			int i = beginL; /* 维护tmp */

			if (beginR >= n)
				break;

			if (endR >= n)
				endR = n - 1;

			while ((curL <= endL) && (curR <= endR))
			{
				if (a[curL] < a[curR])
					tmp[i++] = a[curL++];
				else
					tmp[i++] = a[curR++];
			}

			while (curL <= endL)
				tmp[i++] = a[curL++];

			while (curR <= endR)
				tmp[i++] = a[curR++];

			memcpy(a + beginL, tmp + beginL, sizeof(int) * (endR - beginL + 1));
		}

	}

	free(tmp);
}

三、计数排序(时间:O(N))

1、思路

思路很简洁,就是新开一个数组用来储存原数组每个元素出现的次数,然后根据映射规律从小到大把值赋到原来的数组上。

2、使用条件

但是,计数排序的适用条件非常苛刻。只能对整型,且数据的极差不大的数组进行排序。

3、代码

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];

		if (a[i] < min)
			min = a[i];
	}

	int* tmp = (int*)malloc(sizeof(int) * (max - min + 1));
	assert(tmp != NULL);
	memset(tmp, 0, sizeof(int) * (max - min + 1));

	for (int i = 0; i < n; i++)
	{
		tmp[a[i] - min]++;
	}

	int j = 0;
	for (int i = 0; i < max - min + 1; i++)
	{
		while (tmp[i]--)
		{
			a[j++] = i + min;
		}
	}

	free(tmp);
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胎粉仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值