new subject

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));
  }
}

猜你喜欢

转载自blog.csdn.net/hxy17682323970/article/details/88823268
new
今日推荐