学裕 的个人资料土拨鼠照片日志列表 工具 帮助
12月12日

一个给计算机系学生面试的算法题目,很少学生能在 2min里解答

一个很大的文件,存放10G个整数的数列,该数列是乱序排列。
问题是要找出其中的中位数。

因为文件很大,10G*4Byte = 40GB的大小。无法一次读入内存一次处理完。需要学生有一些分步骤处理的算法思考。
----------------------------------------------------------------------------
Tony 出的这个题目对于面试可能不合适,或许放笔试会好些。
这里给大家练习一下,2min 的时间。能解决的举手。

=======================================================================================

2006-12-12

偶认为可以用来判断计数,从最高位到最低位,为了方便表述我们假设为无符号整数,即0x00000000~0xFFFFFFFF依次递增,那么可以遍历所有数据,并记录最高位为01个数(最高位为0的肯定是小于最高位为1的)记为N0、N1

那么根据N0N1的大小就可以知道中位数的最高位是0还是1

假设N0>N1,那么再计算N00N01

如果N00>(N01+N1),则说明中位数的最高两位00

再计算N000N001.。。。依次计算就能找到中位数

优化

为了减少遍历次数,可以通过提高比较的粒度来实现,以16位为计算粒度,例如第一次遍历计算

{N0000000000000000,
 N0000000000000001,
 N0000000000000010,
.。。。。。
 N1111111111111110,
 N1111111111111111},计算多少位可以根据内存的承受量来确定

内存使用2^16*64(应该是这么算吧,计数器大小为64位)应该不大吧

然后根据这些计数就可以知道中位数的前16位了,后面的我就不用说了,再确定后16位就行了。

不知道这样做是否正确,欢迎讨论!

一下是代码VC6可以编译通过,没有任何测试(包括逻辑的正确性),没有读文件部分

 
 
#define COUNTER_SIZE 0x10000    //16位为粒度
#define HALF_SIZE    0x500000000 //没写错的话应该是5G=10G/2

unsigned int
readANum()
{

    //这里可以用缓存提高速度
    return 0;
}


void
calHi16BitCounter(long * counter){
    unsigned int
num = 0;
    int
countLooper = COUNTER_SIZE + 1;
    memset(counter, 0, COUNTER_SIZE*sizeof(long));
    while
(--countLooper)
    {

        num = readANum();

        num = (num & 0xFFFF0000) >> 16;
        ++(
counter[num]);
    }

}


void
calLo16BitCounter(long * counter, unsigned int hi16Mask)
{

    unsigned int
num = 0;
    int
countLooper = COUNTER_SIZE + 1;
    memset(counter, 0, COUNTER_SIZE*sizeof(long));
    while
(--countLooper)
    {

        num = readANum();
        if
(!( (num & 0xFFFF0000) ^ hi16Mask ))
        {

            num = (num & 0x0000FFFF);
            ++(
counter[num]);
        }
    }
}


unsigned int
findMidNum(){

    long
*counter = new long[COUNTER_SIZE];
    long
sum = 0;
    unsigned int
midNum = 0;
   
    calHi16BitCounter(counter);

    //计算前16位应该是落在哪个计数器上
    sum = 0;
    long
*loopStart = counter;
    long
*loopEnd   = counter + COUNTER_SIZE;
   
    while
(loopStart < loopEnd)
    {

        sum += *loopStart;
        ++
loopStart;
        if
(sum > HALF_SIZE)
        {

            break
;
        }
    }
    --
loopStart;

    //所以高16位就是loopStart指向的单元的下标
    unsigned int hi16bit = (loopStart - counter)<<16;
    //中位数的位置在以hi16bit开头的数中的位置位
    int loMidPos = (*loopStart) - (sum - HALF_SIZE);

    //下面开始计算以hi16bit为高16位的各个低16位出现的次数
    calLo16BitCounter(counter, hi16bit);

    sum = 0;
    loopStart = counter;
    while
(loopStart < loopEnd)
    {

        sum += *loopStart;
        ++
loopStart;
        if
(sum > loMidPos)
        {

            break
;
        }
    }
    --
loopStart;

    unsigned int
lo16bit = loopStart - counter;
    //就是它了
    midNum = hi16bit | lo16bit;

    delete
[] counter;

    return
midNum;
}