寻找大数组中位数问题(一)

前言

之前在面试的时候,面试官非常喜欢问:你好,请问在一个很大的数组中怎样快速地找出它的中位数?

当时很迷惑,为什么面试官总喜欢找中位数?后来了解到快速排序算法的思想后,发现如果大概知道待排序数组中位数的大小(或者提前找出中位数),将在数量级上提高快速排序算法的效率,这个后面有空再讲。

如果你想先把数组排序,在找出中间那一个,那就。。。。

大家看到算法一定立刻想到时间复杂度和空间复杂度,这是基本的思维方式。我这里提供两个方法,

  1. 方法一提供代码,可以现场给面试官手撕代码(这就直接发offer了);
  2. 方法二提供思路,只要你说出思路来,已经可以征服面试官了。

1.方法一

方法API:

//这里k任意取,如果k=strlen(s)/2,那么就是寻找中位数
char select(char *s, int k);

上面返回的是char,当然也可以为int.

利用快速排序算法的思想,使用切分法来缩小数组的范围。
这个方法能够在线性时间内解决寻找中位数的问题,神不神奇?

1.1第一步:切分

//将数组切分成s[lo .. i-1], s[i], s[i+1 .. hi];
int partition(char *s, int lo, int hi);

结果:它会将数组 s[lo]s[hi] 重新排列,并返回一个整数j,使得:

  1. s[lo ... j - 1] 小于等于s[j],但是s[lo ... j - 1] 内部并不有序;
  2. s[j + 1 ... hi] 大于等于s[j],但是s[j + 1 ... hi] 内部并不有序;
  3. 返回整数j,下一次使用。
int partition(char *s, int lo, int hi) {
	//将数组切分成s[lo .. i-1], s[i], s[i+1 .. hi];
	int i = lo, j = hi + 1;
	char v = s[lo];//数组第一个元素,作为基准元素
	while (true) {
		//扫描左右元素,检查是否需要交换(大于基准元素 和 小于基准元素)
		while (s[++i] < v) { if (i == hi) { break; } }
		while (s[--j] > v) { if (j == lo) { break; } }
		if (i >= j) { break; }

		char temp = s[i];
		s[i] = s[j];
		s[j] = temp;
	}
	//将基准元素放回数组中正确的位置
	char temp = s[lo];
	s[lo] = s[j];
	s[j] = temp;

	//s[lo ..j - 1] <= s[j] <= s[j + 1 ..hi]
	return j;
}

1.2 第二步:寻找

//这里k任意取,如果k=strlen(s)/2,那么就是寻找中位数
char select(char *s, int k);
  1. 如果k = j,问题就解决了;
  2. 如果k < j,继续切分左子数组(令hi = j - 1);
  3. 如果k > j,继续切分右子数组(令lo = j + 1);
char select(char *s, int k) {
	int lo = 0, hi = strlen(s) - 1;
	while (hi > lo) {
		int j = partition(s, lo, hi);
		if (j == k) { return s[k]; }
		else if (j > k) { hi = j - 1; }
		else if (j < k) { lo = j + 1; }
	}
	return s[k];
}

1.3 第三步:完整代码,举例分析

#include<string.h>
#include <stdio.h>
#include <time.h>

#define _CRT_SECURE_NO_DEPRECATE;
#define _CRT_SECURE_NO_WARNINGS;

int partition(char *s, int lo, int hi);
char select(char *s, int k);
int main()
{

	int start;
	start = clock();
	char s1[] = "abcfed";
	char s2[] = "bcadfe";
	char s3[] = "fshskbbsadasdaafsdgfgntyasfafasfahsfkasfapqipowejq1231nkdsk,1213";

	int k = 5;//这里k任意取,如果k=strlen(s)/2,那么就是寻找中位数
	char res = select(s1, k);

	printf("\n");
	printf("   ");
	printf("%c",res);
	printf("\n");

	getchar();
	//return 0;

}

char select(char *s, int k) {
	int lo = 0, hi = strlen(s) - 1;
	while (hi > lo) {
		int j = partition(s, lo, hi);
		if (j == k) { return s[k]; }
		else if (j > k) { hi = j - 1; }
		else if (j < k) { lo = j + 1; }
	}
	return s[k];
}

int partition(char *s, int lo, int hi) {
	//将数组切分成s[lo .. i-1], s[i], s[i+1 .. hi];
	int i = lo, j = hi + 1;
	char v = s[lo];//数组第一个元素,作为基准元素
	while (true) {
		//扫描左右元素,检查是否需要交换(大于基准元素 和 小于基准元素)
		while (s[++i] < v) { if (i == hi) { break; } }
		while (s[--j] > v) { if (j == lo) { break; } }
		if (i >= j) { break; }

		char temp = s[i];
		s[i] = s[j];
		s[j] = temp;
	}
	//将基准元素放回数组中正确的位置
	char temp = s[lo];
	s[lo] = s[j];
	s[j] = temp;

	//s[lo ..j - 1] <= s[j] <= s[j + 1 ..hi]
	return j;
}

1.4分析

1.假设每次都正好将数组二分,那么总比较次数为: N + N 2 + N 4 + N 8 + . . . ≈ 2 N N+\frac{N}{2}+\frac{N}{4}+\frac{N}{8}+... \approx2N N+2N+4N+8N+...2N 式中:N为数组大小。
2.如果 k = N 2 k=\frac{N}{2} k=2N ,那么这个代码就是找中位数,当然也可以找任意第几大(小)的数。

2.方法二

方法二也是基于切分的思想,我们想想,加入现在有100G的数据需要你找出中位数。不能依次放到内存中,你怎么办?

基本知识

  1. 我们都知道,数据在硬盘中都是以二进制储存的,也就是11001011这样。
  2. 从左到右依次是最高位到最低位(不考虑符号位),最左边位为1的肯定比最左边为0的大,(100000000000 >
    011111111111111)。

操作步骤

那么我们建立这样的桶,每进来一个数据,我们就放到相应的桶。

如下是取最高的两位,也就是4个桶:在这里插入图片描述四个桶可以代表4个硬盘,内存里面的代码每次将进来的数分到4个桶里。计算每个桶里面数据的个数。初步确定中位数在哪一个桶里,再对那个桶进行一样的分法,是不是很简单。

思维拓展

当然,你也可以建立8个桶,这样步骤更少,也更快:
在这里插入图片描述

如果觉得好,请不要忘记点赞。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值