• 《编程珠玑》第二章 aha算法


    A题:给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数。

               1、在文件中至少存在这样一个数?

               2、如果有足够的内存,如何处理?

               3、如果内存不足,仅可以用文件来进行处理,如何处理?

    答案:

    1 int 为32位有(expt 2 32) 4294967296 42亿多个数,由于现在数只有40亿,所以必然有整数缺少了。

    2.如果采用bitmap位数思想来存放,则32位整数最多需要占用43亿个位。约512MB的内存空间  (2`32/8=512MB)

                     可以采用前一章的位处理方法。然后判断每个int是否等于-1。因为-1的二进制表示是全1的。如果不等于-1。那么说明某一位没有置位。需要进行处理。

    3.

    我们从表示每个整数的32位的视角来考虑二分搜索,算法的第一趟最多读取40亿个输入,并把起始位为0的整数写入一个顺序文件,

    把起始位为1的写入另一个顺序文件。

    当前输入-->0/1探测---》(分2种:为0的位和为1的位)

    这2个文件中,有一个文件最多包含20亿个整数,我们接下来将该文件用作当前输入并重复探测过程,但这次探测的是第二个位。如果原始的输入文件包含n个元素,那么第一趟将读取n个整数,第二趟最多读取(n/2)个整数,第3趟最多(n/4)个整数。依赖类推,所以总的运行时间正比于n。通过排序文件并扫描也能找到,但是会导致运行时间正比于nlogN。

    (对上面的话不是很认同,假如有很多重复元素怎么办)

    这是一种分治思想:

      按最高位分为两段,没有出现的那个数,肯定在比较小的段里面。

                          如果比较少的段最高位为1,那么缺少的那个数的最高位也为1.

                          如果比较少的段最高位为0,那么少的那个数的最高位也是0.

                          依次按以上方法去处理每个位。

                     算法复杂度为O(n)。每次处理的部分都是上一次的一半。累加之后是O(n).

                     思想与找第K小数的思想是一样的。只不过在这里是有一个自动分割的过程。而找第k小数的时候,是随机找一个数。

    通俗解释:

    那么我们把情况说得简单一点,假设一个文件里头有10个4bit的整数, 
      1 2 3 4 5 7 6 9 8 10 
      那么找出遗漏的数字。因为2^4=16 所以文件中的10个数字必定有遗漏 
       
      那么我取出一个数字,如果是最高位为1,那么放到一个文件1中,否则放到另外一个文件0中。 
      理论上最高位为1的4bit数字有多少?2^3=8个 
      在分拣到文件的过程中,程序有两个计数器,分别记录放入连个文件的数字的个数,如果其中有一个(也可能两个都是)分拣过去的个数小于8个,那么遗漏的数字肯定在这堆里头。 
      分拣出来 
       
      高位为0: 1 2 3 4 5 6 7 
      高位为1: 8 9 10 
       
      高位为0计数器为 7 高位为1计数器为3 显然这两堆都遗漏了数字(比如第一堆遗漏了0) 
      选择遗漏的那一堆,如此再分拣次高位为1跟为0的…… 
       
      磁盘文件可以交替使用,所以占用的空间也不大 
      关键省却了把整个数据文件读到内存撑爆内存排序然后查找的暴力方法
     

      如果有重复的数据怎么办,假设数据是9个7,一个8?

           比如第一次找,
           高位为0,7 7 7 7 7 7 7 7 7
           高位为1,8
           那选择个数少于16/2=8的那组数据继续就能找到,这里对第二堆数据(只有8,说明高位为1的只有一个数)很快就找到了9,10,11,12,13,14,15都是缺失的。

       写的很清楚了,从第一位开始探测,根据是0或1进行分组,对包含的整数个数不大于N/2的序列继续进行探测,只要搜索到某个位数时,有个分组为的整数个数为0,那么便成功找到缺少的数
             考虑一个极端情况,以对3位的整数进行查找为例。
             有如下8个3位的整数:0、1、2、3、4、4、6、7。

             以二进制表示是:

    000

    001
    010

    011
    100
    100
    110
    111

             若用以上方法:则会造成探测第一位的时候,有4个0、4个1,按照以上说法“第二趟最多读取n/2=4个元素”可见,0是符合的,所以,进入第一位为0的4个整数中进行搜索,于是,导致第一步就走错了…
             要探测该序列,在探测第一位时,发现分组个数一样,故无法进行舍弃;在探测第二位时仍需探测全部的8个数,而探测第二位时,0与1的个数仍相同,也无法舍弃;最终只能通过末位进行判定,而对末位,即使知道了有3个是0,5个是1,缺少的是以0结尾的数,也无法确切的知道整个整数是什么,因为第1、2位都无法确定应是1或0.
             之所以会有这种情况,是因为在处理倒数第N位时,
             1、有大于等于2的N次方个数
             2、根据0或1进行分组时,分组包含的数字个数一样,且有个分组包含了所有的01序列组合。(如上面的倒数第三位为0的序列)。
             于是,导致无法进行缩减,若运用作者提供的方法,则很有可能会出现错误。


             回到原题,若判定的后N位数,恰好是这样的情况,该当如何?

    解决:若有这种情况,压根不会进入这个一类中探测。可以模拟一个4位数的数列尝试。

    如果一个序列的后N位至少含2的N次方个不相同的数,那么倒数第n+1位(不论是0或1)的这一类至少有2的n次方个整数,则定不会选择该类继续(若选择该类,说明另一类含有的整数数量不小于他,那么总数一定大于2*2的n次方个,不合题意),故选择到的一类,定是缺了某个数的一类。

     上面转自:http://blog.csdn.net/burningsheep/article/details/7814591

    一篇文章:

    分析:32位整数一共有4294967296个,略大于40亿。即使不重复出现,它们也不可能全部放入这40亿个整数的数组中,必然有一部分不出现。根据二分思想,我们把40亿个数的集合分成两个,其中必然有一个至少缺少一个数的集合,进行递归求解。划分的依据是按数的位扫描,从第31位开始,分别统计这一位是0和1的数,把较小的那一部分用做下一次递归。扫描完第0位,必然得到一个不含元素的空集,这个集合对应的就是缺失的元素。

      为了演示这一过程,我编写了相应的测试程序。由于包含大量的文件I/O操作,看上去比较复杂,但是基本的思想框架是一样的。为了简化起见,只处理30000个带符号的正数(这意味着我从每个数的第14位开始检测,最多有37628个可能),运行前需要生成一个含有30000个数的文件output.txt。

     
    #include <stdio.h>
    #include <assert.h>
    
    int BitCheck(int total,int n,int last) {
        FILE *input,*output0,*output1;
        char filename[10] = "";
        int mask,value,num0 = 0,num1 = 0;
        assert(n>=0);
        if(n==total)
            input = fopen("40亿output.txt","r");
        else {
            sprintf(filename,"%d_%d.txt",n,last);
            input = fopen(filename,"r");
        }
        if(n==0) {
            sprintf(filename,"final_0");
            output0 = fopen(filename,"w");
            sprintf(filename,"final_1");
            output1 = fopen(filename,"w");
        }
        else {
            sprintf(filename,"%d_0.txt",n-1);
            output0 = fopen(filename,"w");
            sprintf(filename,"%d_1.txt",n-1);
            output1 = fopen(filename,"w");
        }
        assert(input!=NULL && output0!=NULL&&output1!=NULL);
        mask = 1<<n;
        while(!feof(input)) {
            fscanf(input,"%d
    ",&value);
            if(value&mask) {
                fprintf(output1,"%d
    ",value);
                num1++;
            }
            else {
                fprintf(output0,"%d
    ",value);
                num0++;
            }
        }
        fflush(output0);
        fflush(output1);
        fclose(output0);
        fclose(output1);
        fclose(input);
        return num1<num0;
    }
    
    int Search(int n){
        int total = n,last = 0,missing =0;
        while(n>=0) {
            last = BitCheck(total,n,last);
            missing |= (last<<n);
            n--;
        }
        printf("missing number:%d
    ",missing);
        return 0;
    }
    
    
    int main() {
        Search(14);
        return 0;
    }

    上面的代码,第14位开始检测,最多有37628个可能,因为2^14+2^13+.......1为32768.所有从第14为开始检测完全可以查找

    30000里面丢失的数。

    last=BitCheck(total,n,last)

    last为上次检查的结果,0或1.

    while循环一直到n为0为止。

    转自:http://www.cnblogs.com/wuyuegb2312/p/3139926.html

    http://hi.baidu.com/banyantree/item/904da2e81626bf0e570f1d3e
    http://blog.csdn.net/ju136/article/details/6839100
    http://www.cnblogs.com/wuyuegb2312/p/3139926.html

  • 相关阅读:
    二分查找算法
    java多线程模拟停车位问题
    ECShop模板原理
    linux 常用命令 自己 积累
    ubuntu忘记root密码解决
    centos下一键安装lamp环境,快捷,方便
    腾讯php部分面试题答案
    PHP面试题集
    php笔试题
    流行的php面试题及答案
  • 原文地址:https://www.cnblogs.com/youxin/p/3419685.html
Copyright © 2020-2023  润新知