文章最后修改时间: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 == lenB 而 ia < 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);
}