给你几亿个QQ号,怎样快速去除重复的QQ号?

可以作如下假定:

QQ号数字范围从0到十亿,即[0, 1000000000),且最多给你10亿个QQ号,这些QQ号放在1或多个文本文件中,格式是每行一个QQ号。

请读者先独立思考一下该怎样解决。

————————————————————————————————————————————————————————

其实在一年前碰过类似的问题,当时的解决方案: 借助hash算法思想,把一个大文件哈希分割到多个小文件中,而哈希冲突的数字

一定会在同一个小文件中,从而保证了子问题的独立性,然后就可以单独对小文件通过快速排序来去重

——这样就通过分而治之解决了几G数据进行内排序的问题。

虽然哈希分割文件是O(n)的时间复杂度,但总的效率仍是依从快速排序的时间复杂度O(logn)。

另外,分而治之有个好处就是借助子问题的独立性可以利用多核来做并行处理,甚至做分布式处理。

后来小菜在 《编程珠玑》 中看到了位图这个数据结构可以很方便地处理此类问题,时间复杂度可以达到了O(n)

那怎么实现这个数据结构呢?

位图的原理类似我们常用的标记数组map[]/vis[],比如map[i] = 1表示把第i个元素标记为1,按照这种思想来去重是很简单的。

现在假定QQ号数字范围是[0, 10亿),则要申请10亿个char元素用来做标记,那么进程就需要1G的运行内存。

那如果数字范围增大到100亿,一般的计算机可能就吃不消了。

位图数据结构只需要1/8的空间, 节省7/8的内存是非常可观的 。

因为标记只有1和0两个值,所以可以 只用一个比特位来做标记 。

设有char类型数x,1字节包括8个位,我们可以申请char bit_map[10亿/8+1]的空间,就足以给范围在[0,10亿)的数字去重了。

选择char类型 而不是int等其它类型是考虑到,C标准规定任何实现都要保证char类型占1个字节。

+1 ,是考虑到C整型除法向下取整的特点,比如100/8结果为12,这样访问编号>=96的比特位(设从0开始编号),就会发生数组越界。

我们知道位图的数据结构就是一个数组,而 位图的操作(算法) 基本依赖于下面3个元操作

set_bit(char x, int n); //将x的第n位置1,可以通过x |= (x << n)来实现

clr_bit(char x, int n); //将x的第n位清0,可以通过x &= ~(1 << n)来实现

get_bit(char x, int n); //取出x的第n位的值,可以通过(x >> n) & 1来实现

有了上面3个元操作,位图的具体操作就简单了——

比如,要对数字int x = 1848105做标记,就可以调用set_bit(bit_map[x/8], x%8);

除法看做求“组编号”,x/8即是 以8个位为一个小组,分组到编号为 idx = x/8 的bit_map元素中,然后在组内偏移 lft = x%8 个比特位。

考虑到这些操作是非常频繁的,所以把上述三个方法改写成宏减少函数调用的开销,并且把x/8改为x<<3,x%8改为x&7。

经过上面的分析,写代码就很不难了——

 1 /*
 2  *CopyRight (C) Zhang Haiba
 3  *File: 1billon_remove_duplicate_not_sort.c
 4  *Date: 2014.03.11
 5  */
 6 #include <stdio.h>
 7 #include <string.h>
 8 #include <stdlib.h>
 9 #define MAP_LEN (1000000000/8 + 1)
10 #define BUF_SIZE 10
11 #define SET_BIT(x, n) ( (x) |= (1 << (n)) )
12 #define GET_BIT(x, n) ( ((x)>>(n)) & 1 )
13
14 char bit_map[MAP_LEN];
15
16 int main(int argc, const char *argv[])
17 {
18     FILE *ifp, *ofp;
19     int idx, lft, x;
20     char buf[BUF_SIZE]; //cut if number length > BUF_SIZE ex. range[0, 1000000000) then BUF_SIZE=10
21
22     if (argc == 1) {
23         fprintf(stderr, "usage: %s inputfile1 inputfile2 ...\n", argv[0]);
24         exit(1);
25     } else {
26         ofp = fopen("./output.txt", "w");
27         for (idx = 1; idx <= argc; ++idx) {
28             if ( (ifp = fopen(argv[idx], "r")) == NULL ) {
29                 fprintf(stderr, "%s: can not open %s\n", argv[0], argv[idx]);
30                 exit(1);
31             }
32             printf("processing the %dth file...\n", idx);
33             while ( fgets(buf, sizeof buf, ifp) != NULL ) {
34                 sscanf(buf, "%d", &x);
35                 idx = x >> 3;
36                 lft = x & 7;
37                 if (GET_BIT(bit_map[idx], lft) == 0) {
38                     bit_map[idx] = SET_BIT(bit_map[idx], lft);
39                     fprintf(ofp, "%d\n", x);
40                 }
41             }
42             fclose(ifp);
43         }
44         fclose(ofp);
45     }
46     return 0;
47 }

【测试用例1:】

ZhangHaiba-MacBook-Pro:KandR apple$ time ./a.out  input2.txt
processing the 1th file...real    0m0.028s
user    0m0.001s
sys    0m0.002s

输入输出文件对比:

由于实现中故意使用了fgets(),可以防止输入文本中 长度不合法 的数据

对于长度超过限制,则进行 截断处理(见上图左边第一行) ,同时可以达到滤空的效果。

【测试用例2:】

我们可以写一个小程序生成N个范围[0, 10亿)的数字,也就是最大的数是包含9个9的999999999。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define MAX 1000000000int main(void)
{
  srand((unsigned)time(NULL));  fprintf(stderr, "this prog output N random numbers to stdout.\nlease enter the value of N:\n");
  int n, i;
  scanf("%d", &n);
  for (i = 0; i < n; ++i)
    printf("%d\n", rand()%MAX);
  return 0;
}

通过这个程序生成 1亿个随机数并重定向输出到input1.txt,则这个文本文件大概有970Mb ,然后执行测试

ZhangHaiba-MacBook-Pro:KandR apple$ time ./a.out  input1.txt
processing the 1th file...real    1m12.263s
user    1m0.716s
sys    0m2.685s

耗时1分12秒,速度飞快!

如果需要输出的文本内容是 有序的 ,稍作修改即可——

 1 /*
 2  *CopyRight (C) Zhang Haiba
 3  *File: 1billon_remove_duplicate_sort.c
 4  *Date: 2014.03.11
 5  */
 6
 7 #include <stdio.h>
 8 #include <string.h>
 9 #include <stdlib.h>
10 #define MAP_LEN (1000000000/8 +1)
11 #define BUF_SIZE 10
12 #define CLR_BIT(x, n) ( (x) &= ~(1 << (n)) )
13 #define SET_BIT(x, n) ( (x) |= (1 << (n)) )
14 #define GET_BIT(x, n) ( ((x)>>(n)) & 1 )
15
16 char bit_map[MAP_LEN];
17
18 int main(int argc, const char *argv[])
19 {
20     FILE *fp;
21     int idx, lft, x;
22
23     char buf[BUF_SIZE]; //cut if number length > BUF_SIZE ex. range[0, 1000000000) then BUF_SIZE=10
24     if (argc == 1) {
25         fprintf(stderr, "usage: %s inputfile1 inputfile2 ...\n", argv[0]);
26         exit(1);
27     } else {
28         //memset(bit_map, 0, sizeof bit_mape);
29         for (idx = 1; idx <= argc; ++idx) {
30             if ( (fp = fopen(argv[idx], "r")) == NULL ) {
31                 fprintf(stderr, "%s: can not open %s\n", argv[0], argv[idx]);
32                 exit(1);
33             }
34             printf("processing the %dth file...\n", idx);
35             while ( fgets(buf, sizeof buf, fp) != NULL ) {
36                 sscanf(buf, "%d", &x);
37                 idx = x >> 3;
38                 lft = x & 7;
39                 bit_map[idx] = SET_BIT(bit_map[idx], lft);
40             }
41             fclose(fp);
42         }
43
44         fp = fopen("./output.txt", "w");
45         printf("output to file: output.txt...\n");
46         for (idx = 0; idx < MAP_LEN; ++idx) {
47             for (lft = 0; lft < 8; ++lft)
48                 if (GET_BIT(bit_map[idx], lft) == 1)
49                     fprintf(fp, "%d\n", (idx<<3)+lft);
50         }
51         fclose(fp);
52     }
53     return 0;
54 }

实际测试发现,对于很小的输入文本(例如空文本),这种方法也需要3~4秒的本机执行时间用于遍历输出。

但对于上面将近1G的输入文本文件,测试时间与不排序的实现方案相差无几,甚至略快一点。

怎样对10亿个数字快速去重?——浅析位图数据结构及其应用相关推荐

  1. 编写一个程序,从10亿个数字的数组中找出100个最大的数字

    本文翻译自:Write a program to find 100 largest numbers out of an array of 1 billion numbers I recently at ...

  2. 从10亿个数字中找出最大的前100个数

    先拿10000个数建堆,然后一次添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个 ...

  3. 怎么对10亿个电话号码进行去重

    方法一: 通过哈希算法,将10亿个电话号码按照哈希值落在多个文件中, 重复的电话号码有相同的哈希值,肯定位于一个文件中, 这样就可以分别对每个文件排序删除重复的电话号码. 方法二: 使用位图来进行处理 ...

  4. 堆排序--10亿个数字取出最大或者最小的10个数字

    github地址: https://github.com/luolaifa000/phpStudyCode/blob/master/HeapSort.php  <?php function pr ...

  5. 10亿数据中取最大的100个数据

    思路1:利用堆排序实现 (1)取前m个元素(例如m=100),建立一个小顶堆.保持一个小顶堆得性质的步骤,运行时间为O(lgm);建立一个小顶堆运行时间为m*O(lgm)=O(m lgm); (2)顺 ...

  6. 有10亿个杂乱无章的数,怎样最快地求出其中前1000大的数

    1)方法1 建一个1000个数的最小堆,然后依次添加剩余元素,如果大于堆顶的数(堆中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的1000个数就是所需的最大的100 ...

  7. 计算机说唱卖苹果是谁,苹果音乐第一张播放量过 10 亿的专辑,来自说唱歌手 Drake...

    一天前,来自加拿大的说唱歌手 Drake 在 Instagram 上发布了一张照片,Drake 伸手勾住了苹果 CEO 蒂姆·库克的肩膀.他们身前的一张海报说明了他们庆祝的原因,Drake 在今年 5 ...

  8. Java快速生成20亿数字_关于内存:Java-打印10亿到20亿

    我想打印1000000至1999999999. 我以为这是一件容易的事,但是蚀告诉了我一些有关内存错误的信息. 我该怎么办? 我想要一个文本数据,其中所有数字都来自 将1000000000至19999 ...

  9. 对10亿个数据去重java_JAVA 8 新特性

    JAVA 8 新特性 一.Lambda 表达式 Consumer Predicate Function Supplier 二.stream 流 1. 获取流 2. 中间操作 1.1)map 把对应的操 ...

最新文章

  1. Android关于Task的一些实践之SingleTask, SingleInstance和TaskAffinity
  2. expdp数据泵导出操作
  3. WAIC2020开幕在即,第四范式亮点抢先看
  4. 许家印帮贾跃亭广州拿地造车,这是要翻身的节奏吗?
  5. html按钮的下拉菜单,按钮下拉菜单
  6. 使用决策树分类算法判断一下你的Python水平
  7. SpringSecurity-1-前言,登录原理
  8. python 画风场 scipy_Python库之SciPy教程
  9. 整型索引查询mysql是不是快点_图解Mysql索引的数据结构!看不懂你来找我
  10. Premiere教程,认识 Pr 以及电脑配置
  11. MySQL Err126错误[Err] 126 - Incorrect key file for table '.\device\table_name.MYI'; try to repair it
  12. 数据挖掘概念与技术学习笔记(1)
  13. 数据结构视频教程 -《[中山大学]算法与数据结构(C语言版)[胡青主讲]》
  14. C++ Primer 5th Edition(英文版)kindle.mobi
  15. 数量关系-经济利润问题
  16. cyj等于什么英语单词_英语参考单词读写规律大全.doc
  17. Docker使用总结
  18. OHIF记录(二)——Viewers和React-vtk工具包互联
  19. 聚焦“教-学-评-测-练-管一体化”,推动新型人才培养
  20. allegro如何导入嘉立创可以识别的bom

热门文章

  1. 【USACO题库】1.2.1 Milking Cows挤牛奶
  2. 机器学习之深入理解K最近邻分类算法(K Nearest Neighbor)
  3. 3.5 杭电复试题 2006
  4. RPC or noRPC,这是个问题
  5. 常见面试题整理--数据库篇(每位开发者必备
  6. 爬一爬妹子网,看看妹子
  7. 【国产FPGA】国产FPGA搭建图像处理平台
  8. ios 通讯录 通过电话号码 查询姓名
  9. java 百度地图_Java web实现百度地图导航
  10. 码农微信公众账号推荐