算法篇:二路归并


文章最后修改时间:2020-08-30 18:55

二路归并

  将两个有序数列组成一个新的有序数列。
  从最小端开始,哪边小就取哪边放入新序列中,最后得到的序列依然是有序的。
  若两个有序数列长度分别为N和M, 则归并的时间复杂度为   O ( N + M ) \ O(N+M)  O(N+M), 空间复杂度   O ( N + M ) \ O(N+M)  O(N+M)

开辟新数组并进行归并

  假设两个有序数列分别在数组a和数组b中,合并后保存到另一个数组c。中

写法1

  • 重点在归并时的判断,当数组b已完成,或者数组a未完成且数组a这边比较小时选择数组a
  • (ib >= lenB) 需放在 (ia < lenA) && (a[ia] <= b[ib]) 前面, 不可交换。如果放在后面,当 ib == lenBia < lenA 时,(a[ia] <= b[ib]) 将会越界访问数组b
  • 当两边都相等时,a优先。
int* merge(int a[], int lenA, int b[], int lenB)
{
	int len = lenA + lenB;
	int* c = (int*)std::malloc(len * sizeof(int));
	int ia = 0, ib = 0;
	for (int ic = 0; ic < len; ic++) {
		if ((ib >= lenB) || (ia < lenA) && (a[ia] <= b[ib]))
			c[ic] = a[ia++];
		else
			c[ic] = b[ib++];
	}
	return c;
}

写法2

  • 先归并直到有一路完成,然后再将另一路剩余部分直接放在末尾
  • 代码比上面那种多一些
int* merge(int a[], int lenA, int b[], int lenB)
{
	int len = lenA + lenB;
	int* c = (int*)std::malloc(len * sizeof(int));

	int ia = 0, ib = 0, ic = 0;
	//归并直到有一路完成
	while (ia < lenA && ib < lenB) {
		if (a[ia] <= b[ib])
			c[ic++] = a[ia++];
		else
			c[ic++] = b[ib++];
	}
	//剩余部分直接加入末尾
	while (ia < lenA)
		c[ic++] = a[ia++];
	while (ib < lenB)
		c[ic++] = b[ib++];

	return c;
}

归并至原空间

假设两个都在数组a中,且内存连续,分别为a[left] ~ a[m]和a[m+1] ~ a[right],要将数组归并后保存到原来的位置,则需要额外的空间。

普通归并

  • 归并到一个临时数组中,再将新序列复制回原来的位置
void merge(int a[], int left, int m, int right)
{
	int lenA = m - left + 1, lenTemp = right - left + 1;
	//开辟临时数组
	int* temp = (int*)std::malloc(lenTemp * sizeof(int));

	//归并到临时数组中
	int ia = left, ib = m + 1;
	for (int k = 0; k < lenTemp; k++) {
		//不越界并且小于另一边,或者另一边越界
		if ((ib > right) || (ia <= m) && (a[ia] <= a[ib]))
			temp[k] = a[ia++];
		else
			temp[k] = a[ib++];
	}

	//拷贝回数组a
	for (int i = 0; i < lenTemp; i++)
		a[i + left] = temp[i];

	free(temp);
}

哨兵牌法

  • 开辟临时数组,将两个有序数列复制过去,并在末尾放置哨兵牌(无穷大)。
  • 然后再将两个有序数列归并到原来的数组中。
  • 因为原数列是非降序的,末尾的哨兵牌为无穷大,所以归并时不会取到哨兵牌,不用担心数组越界的问题,所以无需越界的判断。
void merge(int a[], int left, int m, int right)
{
	int lenA = m - left + 1, lenB = right - m;
	//开辟临时数组,比原数组多1(用来放哨兵牌),并将数据拷贝过去
	int* L = (int*)std::malloc((lenA + 1) * sizeof(int));
	int* R = (int*)std::malloc((lenB + 1) * sizeof(int));
	for (int i = 0; i < lenA; i++)
		L[i] = a[i + left];
	for (int i = 0; i < lenB; i++)
		R[i] = a[i + m + 1];

	//放置无穷大哨兵牌,可简化代码(不用担心有数组越界, 少了一些判断)
	L[lenA] = R[lenB] = INT_MAX;

	int i = 0, j = 0;
	for (int k = left; k <= right; k++) {
		if (L[i] < R[j])
			a[k] = L[i++];
		else
			a[k] = R[j++];
	}

	free(L);
	free(R);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

依稀_yixy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值