• 第 2 章 第 2 题 找" 重数/漏数 "问题 位向量实现


    问题分析

      输入:一个包含了4 300 000 000个32位整数的文件( 其中可能有重复出现的数字 )

      输出:一个在这个文件中重复出现过了的数字

      约束:无

    解答思路

      第一章中,我们学习了如何用位向量进行一个空间代价很小的排序。在第一章的一些习题中,位向量的每一位代表的数就是该位的位序:位为1时表示存在这个数,0则表示不存在。本题也可以建立一个类似的位向量。但,本题的位向量又有所不同:因为要体现出重复的数据,因此一个数至少要两个位才能表示。故在这个新的位向量中,a ( 位表示的数 )和b ( 位序的关系 )不是a = b而是a * 2 = b1以及a*2 + 1 = b2 ( b1表示a对应的第一个位的位序,b2表示a对应的第二个位的位序 )。而且,位序00表示这两个位序对应的数不存在,位序01表示出现过一次,10表示出现过两次或者两次以上,看清楚包括两次及以上!

      建立好位向量后,就开始检索位向量,将所有“ 10 "位对应的数输出即可。

    效率分析

      空间:4 300 000 000 * 2 ( bit ) ,也就是1GB左右的样子。

      时间:O( N ) ,N这里表示输入文件中数字的个数。

    代码实现

     1 #include <iostream>
     2 #include <fstream>
     3 #include <string>
     4 
     5 using namespace std;
     6 
     7 // 每个整数的位数
     8 #define BITSPERWORD 32
     9 // 每次位移量
    10 #define SHIFT 5
    11 // 取模掩码
    12 #define MASK 0x1F
    13 // 位向量的元素个数
    14 #define N 8600000000 
    15 
    16 // 模拟数组
    17 int a[1 + N/BITSPERWORD];
    18 
    19 /*
    20  * 函数功能:置位向量第i位为1
    21  * 函数说明:" i>>SHIFT "等于" i/32 " 获取到位向量目标位对应的模拟数组元素的位置
    22             " i & MASK "等于" i%32 " 获取到位向量目标位在对应的模拟数组元素中的位置
    23             " 1<<(i & MASK) " 表示一个除了要设置的那位其他位都是0的整数
    24 */
    25 void set (int i) {
    26     a[i>>SHIFT] |= (1<<(i & MASK));
    27 }
    28 
    29 /*
    30  * 函数功能:置位向量第i位为0
    31  * 函数说明:" i>>SHIFT "等于" i/32 " 获取到位向量目标位对应的模拟数组元素的位置
    32             " i & MASK "等于" i%32 " 获取到位向量目标位在对应的模拟数组元素中的位置
    33             " 1<<(i & MASK) " 表示一个除了要清空的那位其他位都是0的整数
    34 */
    35 void clr (int i) {
    36     a[i>>SHIFT] &= ~(1<<(i & MASK));
    37 }
    38 
    39 /*
    40  * 函数功能:获取位向量的第i位
    41  * 函数说明:" i>>SHIFT "等于" i/32 " 获取到位向量目标位对应的模拟数组元素的位置
    42             " i & MASK "等于" i%32 " 获取到位向量目标位在对应的模拟数组元素中的位置
    43             " 1<<(i & MASK) " 表示一个除了要获取的那位其他位都是0的整数
    44 */
    45 int tst (int i) {
    46     return a[i>>SHIFT] & (1<<(i & MASK));
    47 }
    48 
    49 
    50 int main()
    51 {
    52     /*
    53      * 清空位向量
    54     */
    55     for (int i=0; i < N; i++) {
    56         clr(i);
    57     }
    58 
    59     /*
    60      * 打开待处理文件
    61     */
    62     string filename;
    63     cout << "输入数据文件名( 当前目录下 ):";
    64     cin >> filename;
    65     
    66     fstream io;
    67     io.open(filename.c_str());
    68     if (!io) {
    69         cout << "打开文件失败" << endl;
    70     return 1;
    71     }
    72 
    73     /*
    74      * 将数据导入位向量
    75     */
    76     int num;
    77 
    78     while (io >> num) {
    79         if ((tst(2*num) == 0) && (tst(2*num + 1) == 0)) {
    80             set(2*num+1);
    81         }
    82         else if ((tst(2*num) == 0) && (tst(2*num + 1) != 0)) {
    83             clr (2*num);
    84             clr (2*num+1);
    85             set (2*num);
    86         }
    87     }
    88 
    89     /*
    90      * 输出结果
    91     */
    92     for (int i=0; i < 430000000; i++) {
    93         if ((tst(2*i)!=0) && (tst(2*i + 1)==0))
    94             cout << i << endl;
    95     }
    96             
    97     return 0;
    98 } 

    运行测试

      遗憾的是,运行出错了:

      分析了很久,程序中实在找不到问题,只得把这个问题拿去和朋友讨论。所幸某人点出了问题症结所在,令我茅塞顿开:C/C++中,数组在内存空间是连续存放的,而上述代码中的位向量数组要占用大约1GB的连续的空间,而我电脑内存本身才2G,系统分配不了这么大的连续空间给我。于是系统只得报错。另一人更犀利,他指出“ 题目不是只让你找一个重复的数吗?既然找一个就可以了,你何必用两个位来表示一个数呢?”。听到这个让我吐血。。。但,即便一位表示一数,也要用500多MB连续内存空间啊,很多机器都吃不消的。

      为了解决这个问题,我必须想到一种内存消耗更小的算法,而不是去电脑城买内存条。

      

  • 相关阅读:
    进度条
    html5 表单新增事件
    html5 表单的新增type属性
    html5 表单的新增元素
    html5 语义化标签
    jq 手风琴案例
    codeforces 702D D. Road to Post Office(数学)
    codeforces 702C C. Cellular Network(水题)
    codeforces 702B B. Powers of Two(水题)
    codeforces 702A A. Maximum Increase(水题)
  • 原文地址:https://www.cnblogs.com/scut-fm/p/3329546.html
Copyright © 2020-2023  润新知