[算法入门笔记] 16. 大数据

0 建议

大数据类型题目技巧

  • 哈希函数可以把数据按照种类均匀分流

  • 布隆过滤器用于集合的建立与查询,并可以节省大量空间

  • 一致性哈希解决数据服务器的负载管理问题

  • 利用并查集结构做岛问题的并行计算

  • 位图解决某一范围上数字的出现情况,并可以节省大量空间

  • 利用分段统计思想、并进一步节省大量空间

  • 利用堆、外排序来做多个处理单元的结果合并

1 "40"亿个非负整数中找到未出现的数

原始问题

32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数

哈希表不适合作为存储结构的原因

假设使用哈希表保存出现过的数,如果40亿个数不同,则哈希表的记录数为40亿条,存一个32位整数需要4B最差情况需要 40 亿 ∗ 4 B = 160 亿 K B ≈ 16 G B 40亿*4B=160亿KB≈16GB 40亿4B=160亿KB16GB,远远不符合要求

使用位图作为存储结构

使用bit map表示数出现的情况,申请长度为4,294,967,295的bit类型数组 b i t A r r bitArr bitArr b i t A r r bitArr bitArr上每个位置表示01状态。8bit=1B,长度为4,294,967,295的bit类型数组占用500MB

遍历40亿无符号整数,遇到所有的数时就把 b i t A r r [ ] bitArr[] bitArr[]相应位置置1(假设遇到7000,就把 b i t A r r [ 7000 ] bitArr[7000] bitArr[7000]1

遍历完,再次遍历 b i t A r r bitArr bitArr,哪个位置没有置1,这个数就不在这40亿数中(假设 b i t A r r [ 8000 ] = 0 bitArr[8000]=0 bitArr[8000]=0,那么8000就是没出现过的数)

问题进阶

内存限制为10MB,但是只需找到一个没出现的数即可

0~4,294,967,295是可以平均分成64个区间,每个区间67,108,864个数

0区间 ( 0 − 67 , 108 , 863 ) (0-67,108,863) (067,108,863)、第1区间( 67 , 108 , 864 − 134 , 217 , 728 ) 67,108,864-134,217,728) 67,108,864134,217,728)、第i区间 ( 67 , 108 , 864 X i − 67 , 108 , 864 X ( i + 1 ) − 1 ) (67,108,864 X i-67,108,864 X (i+1) - 1) (67,108,864Xi67,108,864X(i+1)1),…,第63区间 ( 4 , 227 , 858 , 432   4 , 294 , 967 , 295 ) (4,227,858,432~4,294,967,295) (4,227,858,432 4,294,967,295)

一共有40亿个数,如果统计落在每一个区间上的数有多少,肯定至少一个区间的计数少于67,108,864,利用这点找到其中一个没出现的数

  • 第一次遍历先申请长度为64的整型数组 c o u n t A r r [ 0...63 ] countArr[0...63] countArr[0...63] c o u n t A r r [ i ] countArr[i] countArr[i]用来统计区间i上的数有多少。

  • 遍历40亿个数,根据当前数多少决定在哪个区间上计数增加

    如果当前数是 3422 , 552 , 090 3422,552,090 3422,552,090 3422 , 552 , 090 / 67 , 108 , 864 = 51 3422,552,090/67,108,864=51 3422,552,090/67,108,864=51,所以第51区间计数增加 c o u n t A r r [ 51 ] + + countArr[51]++ countArr[51]++

  • 遍历完40亿数后,遍历countArr,必然有一个位置 c o u n t A r r [ i ] countArr[i] countArr[i]小于 67 , 108 , 864 67,108,864 67,108,864,表示第i区间至少一个数没出现过

    此时使用内存 64 ∗ 4 B 64*4B 644B

假设找到第37区间的计数小于 67 , 108 , 864 67,108,864 67,108,864,以下是第二次遍历

  1. 申请长度为 67 , 108 , 864 67,108,864 67,108,864的bitmap(约占用8MB),记为bitArr[0..67,108,863]
  2. 再遍历一次40亿个数,此时遍历只关注落在第37个数,记为 n u m ( n u m / 67 , 108 , 864 = 37 ) num(num/67,108,864=37) num(num/67,108,864=37),其他区间的数全都忽略
  3. 如果步骤2的num在第37区间上,将 b i t A r r [ n u m − 67 , 108 , 864 ∗ 37 ] bitArr[num-67,108,864*37] bitArr[num67,108,86437]的值设置成1。只做第37区间上的数的bitArr映射
  4. 遍历完40亿个数后,在bitArr上必然存在没有设置成1的位置,假设第i位置上的值没有设置成1,那么 67 , 108 , 864 X 37 + i 67,108,864 X 37 + i 67,108,864X37+i这个数就是一个没出现过的数

总结

  • 根据10MB的内存限制,确定区间统计的大小,就是第二次遍历时的bitArr大小
  • 利用区间计数的方式,找到计数不足的区间,这个区间肯定有没出现的数
  • 对这个区间的数做bitmap映射,再遍历bitmap,找到一个没出现的数即可

2 找到"100"亿个URL中重复的URL及搜索词汇的TopK问题

[问题1]
有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL

常规思路
把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件,一直进行划分,直到划分的结果满足资源限制

弄清楚资源限制(内存、计算时间),明确要求后将每条URL通过哈希函数分配到若干机器或拆分成若干小文件(若干指具体资源限制计算出精确的数量)

  • 例如,将100亿个的大文件通过哈希函数分配到100台机器上,然后每台机器分别统计分给自己的URL是否有重复的URL,同时哈希函数的性质决定同一条URL可能分配给不同机器
  • 或者,在单机上将大文件通过哈希函数拆成1000个小文件,对每一个小文件再利用哈希表遍历,找出重复的URL
  • 或者,在分给机器或拆完文件之后排序,排序过后再看是否有重复的URL出现
    [问题2]
    某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门Top100词汇的可行办法

方法1 哈希分流+小根堆

  • 把包含百亿数据量的词汇文件分流到不同的机器上,对每台机器来说,如果分到的数据量依然很大(比如内存不够或存在其他问题,再用哈希函数把每台机器的分流文件拆成更小的文件处理)
  • 处理每个小文件时,通过哈希表统计每种词及词频
  • 哈希表建立完成后,再遍历哈希表,遍历哈希表的过程中使用大小为100小根堆选出每个小文件的Top100(整体未排序的Top100)
  • 每个小文件都有自己词频的小根堆(整体未排序的Top100),将小根堆的词按照词频排序,得到排序后的Top100
  • 把各个文件排序后的Top100进行外排序或者继续利用小根堆,就可以选出每台机器上的Top100
  • 不同机器之间的Top100再进行外排序或利用小根堆
  • 最终求出整个百亿数据量中的Top100

方法2 哈希分流+大根堆

  • 把包含百亿数据量的词汇文件分流到不同的机器上,对每台机器来说,如果分到的数据量依然很大(比如内存不够或存在其他问题,再用哈希函数把每台机器的分流文件拆成更小的文件处理)
  • 处理每个小文件时,通过哈希表统计每种词及词频
    在这里插入图片描述

总结

分流是处理大数据的不错方案

  • 哈希函数将大文件内容分配给不同机器
  • 用哈希函数把大文件拆成小文件,处理每个小数量的集合

大数据经典TopK问题

  • 哈希函数分流
  • 哈希表做词频统计
  • 堆结构和外排序处理

思路剖析

哈希分流成小文件>>小文件词频统计,存储在哈希表中>>小文件计算出各自的TopK>>每个小文件的TopK进行汇总排序,得到大文件的TopK>>每个排序后的大文件的TopK进行排序,得到每台机器的TopK>>不同机器的TopK再排序>>得到全局TopK

3 "40"亿个非负整数中找到出现两次的数和所有数的中位数

[问题1] 32位无符号整数的范围是 0 − 4 , 294 , 967 , 295 0-4,294,967,295 04,294,967,295,现在有40亿个无符号整数,可以使用最多1GB的内存,找出所有出现两次的数。

算法思路

使用bitmap,用两个位置表示一个数的词频,01表示该数出现一次,10表示该数出现两次,11表示该数出现三次及以上,1B占用8bit,长度为 4 , 294 , 967 , 295 ∗ 2 4,294,967,295*2 4,294,967,2952的bit型数组占用1GB

算法实现

  • 遍历40亿个无符号整数,初次遇到num b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1] b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成01
  • 第二次遇到num b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1] b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成10
  • 第三次遇到num b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1] b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成11
  • 以后再遇到num,发现此时 b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1] b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]已经被设置成11,就不再做任何设置
  • 遍历完成后,再次遍历bitArr,如果发现 b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1] b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]被设置成10,那么num就是出现了两次的数
    [问题2]
    可以使用最多10KB的内存,怎么找到这40亿个整数的中位数
    在这里插入图片描述

采用区间统计的方式

  • 根据内存大小和无符号整数数组大小确定分配数组长度
  • 遍历40亿数据, a r r [ i ] arr[i] arr[i]表示第i区间有多少数
  • 通过累加每个区间的出现次数,能找到第20亿个数落在哪个区间
  • 比如0~k-1区间上的数出现了18亿,当发现加上第k区间的数超过了20亿,就可以知道第20亿数是第k区间上的数,并且可以知道是第k区间上的第2亿个数
  • 接下来申请单个区间长度L的无符号整型数组
  • 遍历40亿个数,只关心处在第k区间的数,记为num,将 c o u n t [ n u m − k ∗ L ] + + count[num-k*L]++ count[numkL]++(对第k区间做词频统计)
  • 最后只在第k区间找第2亿个数即可
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan Chau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值