位运算有什么样奇技淫巧?服务器 CPU 占有率高
众所周知的应用:异或筛去出现偶数次的数,树状数组……
有哪些让你印象深刻的位运算技巧?
题主脑海中最先想起来的是,刚学「搜索」时看到的位运算解决⑧皇后的技巧
分享①个利用 bitmap 的去重算法。这个算法时间复杂度可以达到 O(n)。
转自:怎样对①⓪亿个数字快速去重?——浅析位图数据结构及其应用
最近有个朋友问我①个算法题——
给你几亿个 QQ 号,怎样快速去除重复的 QQ 号?
可以作如下假定:
QQ 号数字范围从 ⓪ 到⑩亿,即 [⓪ · ①⓪⓪⓪⓪⓪⓪⓪⓪⓪),且最多给你 ①⓪ 亿个 QQ 号,这些 QQ 号放在 ① 或多个文本文件中,格式是每行①个 QQ 号。
———————————————————————————————————————————
其实在①年前碰过类似的问题,当时的解决方案: 借助 hash 算法思想,把①个大文件哈希分割到多个小文件中,而哈希冲突的数字①定会在同①个小文件中,从而保证了子问题的独立性,然后就可以单独对小文件通过快速排序来去重——这样就通过分而治之解决了几 G 数据进行内排序的问题。
虽然哈希分割文件是 O(n) 的时间复杂度,但总的效率仍是依从快速排序的时间复杂度 O(logn)。
另外,分而治之有个好处就是借助子问题的独立性可以利用多核来做并行处理,甚至做分布式处理。后来小菜在 《编程珠玑》 中看到了位图这个数据结构可以很方便地处理此类问题,时间复杂度可以达到了 O(n) 那怎么实现这个数据结构呢?
位图的原理类似我们常用的标记数组 map[]/vis[],比如map[i] = ① 表示把第 i 个元素标记为 ① · 按照这种思想来去重是很简单的。
现在假定 QQ 号数字范围是 [⓪ · ①⓪亿),则要申请 ①⓪ 亿个 char 元素用来做标记,那么进程就需要 ①G 的运行内存。那如果数字范围增大到 ①⓪⓪ 亿,①般的计算机可能就吃不消了。
位图数据结构只需要 ①/⑧ 的空间, 节省 ⑦/⑧ 的内存是非常可观的 。因为标记只有 ① 和 ⓪ 两个值,所以可以 只用①个比特位来做标记 。
设有 char 类型数 x,① 字节包括 ⑧ 个位,我们可以申请 char bit_map[①⓪亿/⑧+①] 的空间,就足以给范围在 [⓪ · ①⓪亿)的数字去重了。
选择 char 类型 而不是 int 等其它类型是考虑到,C 标准规定任何实现都要保证 char 类型占 ① 个字节。
+① ,是考虑到 C 整型除法向下取整的特点,比如 ①⓪⓪/⑧ 结果为 ①② · 这样访问编号 >=⑨⑥ 的比特位(设从 ⓪ 开始编号),就会发生数组越界。
我们知道位图的数据结构就是①个数组,而 位图的操作(算法) 基本依赖于下面③个元操作
set_bit(char x, int n); //将 x 的第 n 位置① · 可以通过 x |= (x n) & ① 来实现
有了上面③个元操作,位图的具体操作就简单了——
比如,要对数字 int x = ①⑧④⑧①⓪⑤ 做标记,就可以调用 set_bit(bit_map[x/⑧], x%⑧);
除法看做求“组编号”,x/⑧ 即是 以 ⑧ 个位为①个小组,分组到编号为 idx = x/⑧ 的 bit_map 元素中,然后在组内偏移 lft = x%⑧ 个比特位。
考虑到这些操作是非常频繁的,所以把上述③个方法改写成宏减少函数调用的开销,并且把 x/⑧ 改为 x BUF_SIZE ex. range[⓪ · ①⓪⓪⓪⓪⓪⓪⓪⓪⓪) then BUF_SIZE=①⓪ if (argc == ①) { fprintf(stderr, \"usage: %s inputfile① inputfile② ...n\", argv[⓪]); exit(①); } else { ofp = fopen(\"./output.txt\", \"w\"); for (idx = ①; idx > ③; lft = x if (GET_BIT(bit_map[idx], lft) == ⓪) { bit_map[idx] = SET_BIT(bit_map[idx], lft); fprintf(ofp, \"%dn\", x); } } fclose(ifp); } fclose(ofp); } return ⓪; }
【测试用例①:】
ZhangHaiba-MacBook-Pro:KandR apple$ time ./a.out input②.txt processing the ①th file...real ⓪m⓪.⓪②⑧suser ⓪m⓪.⓪⓪①ssys ⓪m⓪.⓪⓪②s
输入输出文件对比:
由于实现中故意使用了fgets(),可以防止输入文本中 长度不合法 的数据
对于长度超过限制,则进行 截断处理(见上图左边第①行) ,同时可以达到滤空的效果。
【测试用例②:】
我们可以写①个小程序生成N个范围[⓪ · ①⓪亿)的数字,也就是最大的数是包含⑨个⑨的⑨⑨⑨⑨⑨⑨⑨⑨⑨。
#include #include #include #define MAX ①⓪⓪⓪⓪⓪⓪⓪⓪⓪int 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\", for (i = ⓪; i < n; ++i) printf(\"%dn\", rand()%MAX); return ⓪;}
通过这个程序生成 ①亿个随机数并重定向输出到input①.txt,则这个文本文件大概有⑨⑦⓪Mb ,然后执行测试
ZhangHaiba-MacBook-Pro:KandR apple$ time ./a.out input①.txtprocessing the ①th file...real ①m①②.②⑥③suser ①m⓪.⑦①⑥ssys ⓪m②.⑥⑧⑤s
耗时①分①②秒,速度飞快!
如果需要输出的文本内容是 有序的 ,稍作修改即可——
/* *CopyRight (C) Zhang Haiba *File: ①billon_remove_duplicate_sort.c *Date: ②⓪①④.⓪③.①① */#include #include #include #define MAP_LEN (①⓪⓪⓪⓪⓪⓪⓪⓪⓪/⑧ +①)#define BUF_SIZE ①⓪#define CLR_BIT(x, n) ( (x) int main(int argc, const char *argv[]){ FILE *fp; int idx, lft, x; char buf[BUF_SIZE]; //cut if number length > BUF_SIZE ex. range[⓪ · ①⓪⓪⓪⓪⓪⓪⓪⓪⓪) then BUF_SIZE=①⓪ if (argc == ①) { fprintf(stderr, \"usage: %s inputfile① inputfile② ...n\", argv[⓪]); exit(①); } else { //memset(bit_map, ⓪ · sizeof bit_mape); for (idx = ①; idx > ③; lft = x bit_map[idx] = SET_BIT(bit_map[idx], lft); } fclose(fp); } fp = fopen(\"./output.txt\", \"w\"); printf(\"output to file: output.txt...n\"); for (idx = ⓪; idx < MAP_LEN; ++idx) { for (lft = ⓪; lft < ⑧; ++lft) if (GET_BIT(bit_map[idx], lft) == ①) fprintf(fp, \"%dn\", (idx
- 5星
- 4星
- 3星
- 2星
- 1星
- 暂无评论信息
