6-45 归并排序
本题要求实现二路归并排序中的归并操作,待排序列的长度1<=n<=1000。
归并排序思路请参考归并排序详解
本题参考代码:
void Merge(SqList L,int low,int m,int high) {
int i=low, j=m+1;
int buf1[m-low+1];//开辟额外空间,分别存储两部分数据
int buf2[high-m];
while(i<=m) {
buf1[i-low] = L.elem[i];
i++;
}
while(j<=high) {
buf2[j-m-1] = L.elem[j];
j++;
}
i=0,j=0;
int index = low;
//进行数据合并,在两个数组中寻找较小的元素,存入L.elem中
while(i<m-low+1&&j<high-m) {
L.elem[index++] = buf1[i]<buf2[j]?buf1[i++]:buf2[j++];
}
//若仍有数据未合并,则分别处理剩余未合并的两个数组
while(i<m-low+1) {
L.elem[index++] = buf1[i++];
}
while(j<high-m) {
L.elem[index++] = buf2[j++];
}
}
7-46 两个有序序列的中位数
已知有两个非降序序列S1, S2, 求S1与S2归并成一个序列的低位中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。
限制:
时间限制: 700 ms
内存限制: 24 MB
思路一:将A和B合并成新的数组
本思路利用最笨的方法去实现,利用排序将两个数组合并成一个数组,合并成有序数组后即可求任意k值,其时间复杂度为 O(m+n), 空间复杂图为O(m+n)。
本题N最大值为2500000,则输入总空间为250000024(byte)/1024/1024=19.07MB,如果再申请额外空间(即额外的O(m+n)空间),会产生内存不足。
思路二:采用归并方法
为了降低空间,本思路采用归并排序的归并算法,利用两个分别指向A和B数组头的指针去遍历数组,然后统计元素个数,直到找到中位数。本算法是对思路一的改进,用一个临时变量保存k值,而不需要合并数组单独存储,节省了存储空间。
其 时间复杂度为O(m+n), 空间复杂度为O(1).
参考代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX_LEN 2500001
int a[MAX_LEN], b[MAX_LEN];
int main() {
int n,m;
int i,j;
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(i=0;i<m;i++) scanf("%d",&b[i]);
i=0,j=0;
int mid = (n+m-1)/2, cur_index = 0;
int result = 0;
while(cur_index<=mid&&i<n&&j<m) {
result = a[i]<b[j]?a[i++]:b[j++];
cur_index++;
}
while(cur_index<=mid&&i<n) {
result = a[i];
i++;cur_index++;
}
while(cur_index<=mid&&j<m) {
result = b[j];
j++;cur_index++;
}
printf("%d",result);
}
思路三:采用二分方法
当数据量增大时,线性增长也可能不满足时间限制。由于已知两个序列是非递减的,所以就可以将原问题转变成一个寻找第k小数的问题,于是中位数实际上是第(m+n)/2小的数。因此可以将原题目求中位数转换为求第 k = (m+n)/2的数。
首先假设数组A和B的元素个数都大于k/2,我们比较A[k/2-1]和B[k/2-1]两个元素,这两个元素分别表示A的第k/2小的元素和B的第k/2小的元素。这两个元素比较共有三种情况:>、<和=。
如果A[k/2-1]<B[k/2-1],这表示A[0]到A[k/2-1]的元素都在A和B合并之后的前k小的元素中。换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。上述描述证明:假设A[k/2-1]大于合并之后的第k小值,我假定其为第(k+1)小值。由于A[k/2-1]小于B[k/2-1],所以B[k/2-1]至少是第(k+2)小值。但实际上,在A中至多存在k/2-1个元素小于A[k/2-1],B中也至多存在k/2-1个元素小于A[k/2-1],所以小于A[k/2-1]的元素个数至多有k/2+ k/2-2,小于k,这与A[k/2-1]是第(k+1)的数矛盾。
当A[k/2-1]>B[k/2-1]时存在类似的结论。
当A[k/2-1]=B[k/2-1]时,则第k小的数就是这个相等的数据。由于在A和B中分别有k/2-1个元素小于m,所以m即是第k小的数。
通过上面的分析,本思想可以采用递归的方式实现寻找第k小的数。
确定的返回条件:
- 如果A或者B为空,则直接返回B[k-1]或者A[k-1];
- 如果k为1,我们只需要返回A[0]和B[0]中的较小值;
- 如果A[k/2-1]=B[k/2-1],返回其中一个
本思想效率较高。每次都有一半的元素被删除,即计算中值时每次将范围缩小一半,故而时间复杂度为O(lg(m+n)),由于不需要开辟额外空间,本算法空间复杂度为O(1)。
参考代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX_LEN 2500001
int a[MAX_LEN], b[MAX_LEN];
int findKthMinValue(int* a, int n, int* b, int m, int k) {
//always assume that m is equal or smaller than n
if(n>m) return findKthMinValue(b, m, a, n, k);
if(n == 0) return b[k-1];
if(k==1) return a[0]>b[0]?b[0]:a[0];
//divide k into two parts
int pa = k / 2>n?n:k/2, pb = k - pa;
if (a[pa - 1] < b[pb - 1])
return findKthMinValue(a + pa, n - pa, b, m, k - pa);
else if (a[pa - 1] > b[pb - 1])
return findKthMinValue(a, n, b + pb, m - pb, k - pb);
else
return a[pa - 1];
}
int main() {
int n,m;
int i,j;
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(i=0;i<m;i++) scanf("%d",&b[i]);
i=0,j=0;
int mid = (n+m-1)/2, cur_index = 0;
int result = 0;
while(cur_index<=mid&&i<n&&j<m) {
result = a[i]<b[j]?a[i++]:b[j++];
cur_index++;
}
while(cur_index<=mid&&i<n) {
result = a[i];
i++;cur_index++;
}
while(cur_index<=mid&&j<m) {
result = b[j];
j++;cur_index++;
}
printf("%d",result);
if((n+m)&1) {
printf("%d", findKthMinValue(a,n,b,m,(n+m)/2+1));
}else {
printf("%d", findKthMinValue(a,n,b,m,(n+m)/2));
}
}