数据结构笔记10:求两个升序序列的中位数的三种方法(归并排序你会用了吗?)

题目:一个长度为L (L>=1)的升序序列S,处在第[L/2]个位置的数称为S的中位数。例如,若序列S1=(11, 13, 15, 17, 19),则S1的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若S2= (2, 4,6,8, 20),则S1和S2的中位数是11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。要求:

  1. 给出算法的基本设计思想。
  2. 根据设计思想,釆用C或C++或Java语言描述算法,关键之处给出注释。
  3. 说明你所设计算法的时间复杂度和空间复杂度。

该题为2011年研究生考试计算机联考真题。

第一种方法

算法思想:1.先将两个升序序列归并排序成一个升序序列,找中位数(最先想到且简单)
时间复杂度:O(n),空间复杂度:O(n)

#include"head.h"
bool findmid1(SeqList A, SeqList B, SeqList& C) {
     
	if (A.length + B.length > C.Maxsize) {
     
		//return false;//若两表长度之和大于新表最大长度,则返回。
		//为了更有效的执行算法,此处我们不这样写,如果两表长度之和大于新表最大长度,则新表动态增加空间
		IncreaseSize(C, 10);//C表自动增加10个长度
	}
	int i = 0, j = 0, k = 0;
	while (i < A.length && j < B.length)
	{
     
		if (A.data[i] < B.data[j])
		{
     
			C.data[k++] = A.data[i++];
		}
		else
		{
     
			C.data[k++] = B.data[j++];
		}
	}
	while (i < A.length)
	{
     
		C.data[k++] = A.data[i++];
	}
	while (j < B.length)
	{
     
		C.data[k++] = B.data[j++];
	}
	C.length = k;
	int mid = C.length / 2;
	printf("C表中位数为:%d\n", C.data[mid-1]);//中位数,数组长度一般下标-1
	return true;
}

第二种方法

算法思想:类比归并排序的思想但并不实现归并,仅按顺序进行访问
时间复杂度:O(n),空间复杂度:O(1)

int findmid2 ( int* a, int* b, int len ) {
     
	int i = 0, j = 0, k = 0;

	for ( ; k < len-1; k++ ) {
     
		if ( a[i] < b[j] ) {
     
			i++;
		}
		else {
     
			j++;
		}
	}

	return (a[i] < b[j])? a[i]: b[j];
}
//就是从头到尾一共数 len 个数,这个时候两个指针指向的数字较小者即为所求。

第三种方法

**王道考研参考书中给出的该题的最优方法

时间复杂度:O(log2n),空间复杂度:O(1)

分别求两个升序序列A、B的中位数,设为a和b, 求序列A、B的中位数过程如下:

  1. 若a = b,则a或b即为所求中位数,算法结束。

  2. 若a < b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等。

  3. 若a > b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等。**

int findmid3(int A[], int B[], int n)
{
     
	int start1 = 0, end1 = n - 1, m1, start2 = 0, end2 = n - 1, m2;
	//分别表示序列A和B的首位数、末位数和中位数

	while (start1 != end1 || start2 != end2)
	{
     
		m1 = (start1 + end1) / 2;
		m2 = (start2 + end2) / 2;
		if (A[m1] == B[m2])
			return A[m1];   //满足条件 1)

		if (A[m1] < B[m2]) // 满足条件 2)
		{
     
			if ((start1 + end1) % 2 == 0)  //若元素个数为奇数
			{
     
				start1 = m1;  //舍弃A中间点以前的部分且保留中间点
				end2 = m2;  //舍弃B中间点以后的部分且保留中间点
			}
			else				//元素个数为偶数
			{
     
				start1 = m1 + 1;  //舍弃A中间点及中间点以前部分
				end2 = m2;  //舍弃B中间点以后部分且保留中间点
			}
		}
		else
		{
       //满足条件3)
			if ((start2 + end2) % 2 == 0)   //若元素个数为奇数
			{
     
				end1 = m1;    //舍弃A中间点以后的部分且保留中间点
				start2 = m2;    //舍弃B中间点以前的部分且保留中间点
			}
			else     //元素个数为偶数
			{
     
				end1 = m1;    //舍弃A中间点以后部分且保留中间点
				start2 = m2 + 1;    //舍弃B中间点及中间点以前部分
			}
		}
	}
	return  A[start1] < B[start2] ? A[start1] : B[start2];
}

总结

  1. 当在练习的时候可以对三种方法慢慢调试,仔细理解。

  2. 若无法理解第三种最优解的方法建议就不要再看了,考试时第三种方法为满分答案,第二种方法减1分,第一种方法减2分。

  3. 小编在写第一种和第二种方法时一共用了10分钟,第三种方法依然没有理解并成功运行,给诸位大佬程序员丢人了…

你可能感兴趣的:(数据结构笔记——线性表,算法,数据结构)