资源限制问题一般不会手撕代码
1 认识哈希函数:
- 输入参数data,假设是int类型,特征:可能性无穷大,比如str类型的参数
- 输出参数类型out,特征:可能性可以很大,但一定是有穷尽的
- 哈希函数没有任何随机的机制,固定的输入一定是固定的输出,相同的输入一定会是相同的输出,不同的输入导致输出离散化而不相同
- 输入无穷多但输出值有限,所以不同输入也可能输出相同(哈希碰撞,很少发生)
- 再相似的不同输入,得到的输出值,会几乎均匀(离散和均匀一个道理)的分布在out域上(重点)
- 输出结果取余也是均匀分布,碰撞后厚度也是均匀分布。
扩容logN次
桶里面放有序数据,可以用哈希值排序
2 布隆过滤器
布隆过滤器:没有删除的黑名单系统
应用场景:有病毒网站进黑名单,可在黑名单查询该网站是否用户能进?爬虫爬取,是否爬取过这个网站?
好处:建立黑名单并且可以节省空间
节约空间。
- 利用哈希函数的性质
- 每一条数据提取特征
- 加入描黑库
2.1 原理
位图原理:
一个状态就是一个bit
位图有m个长度,实际的使用空间是m/8的字节数。
# 位图实现
# 位图实现
# 3200 bit bit[0~3199]
arr = [0] * 100 # 数组里面一位数为32bit
# arr[0] -> 32bit int 0(int)
# arr[1] -> 32bit int 0(int)
position = 453
# arr[int(453/32)] = arr[14] -> 010101000101 0 11010 32位
# 为了提取(453%32) = 5 ,提取第5位状态 第0位开始数 第5位是0
# arr[14] >> 5 010101000101 0 11010 >> 5 整体右移5位,0就来到最右侧
# 最右侧和1与一下,0 & 1 = 0
# arr[int(453/35)] 向右移动 (453%32) 这么多位置 和1与一下,就知道状态是0还是1
status = ((arr[int(453/32)] >> (453%32)) & 1) # 得出543位的状态了
# 453位的状态设置为1,一定改变这个数字某一位状态
# 453在哪个数上 453/32位置上
# 1向左移动5位, 1 << (453 % 32) 只在第5位上有1
# 或 原始数,就将453位设置为1
arr[int(453/32)] = arr[int(453/32) | (1 << (453 % 32))]
--------------------------------------------------
# 为了装更大的位数,可以用二维数组
# 32000bit
# matrix[0] -> 100 3200 列有100,每一个32位,行有100
matrix = ([0] * 100 for _ in range(100))
position = 170039
# 170039 在 170039 / 3200 这一行
# 在53行第几个数 170039 % 3200 是剩余的比特位 ,对剩余的bit位 /32 就是这一行第几个数字 %32 就是定位第几个bit
# 下标不够用,通过这种方式扩展
2.2 过滤器原理
先创建一个都是0的m长位图,某个位置变成就就当成描黑的状态。
url算出哈希值%m得到一个值,这个值代表位图位置,将位图该位置描黑;该url再取另一个哈希函数算出哈希值%m,再对位置描黑。如果有k个哈希函数,就会描黑k个位置,有可能k个位置是重的,就说明url加入了黑名单。
用k个哈希函数%m,算出k个位置,如果这些位置都是黑,说明此url是黑名单中。
k个哈希值描黑是特征,混在一起是为了省空间。
存在问题:
- 选几个哈希函数合适(k)?
- 位图到底选多大(m)?
不需要知道单样本大小,由样本数量决定m大小。
有失误率的:100亿个url描1000长度位图,估计都能描黑。
比如:爬数据,可以允许失误,少爬一个数据。
不是黑名单的东西可能误报黑名单,但是黑名单的一定会报告黑名单。
黑名单必须先给,不能超过什么量。可以动态来,爬虫就是动态的。
2.3 布隆过滤器设计
布隆过滤器只需要知道,样本量N和失误率P。
设计出的过滤器只能失误率比P低。
选个合适的m,既省空间,也能保证失误率低,不需要无限增加m。
M和N已经确定,K和失误率P的关系,K太多了,描黑的就多了,m会迅速耗尽。
m有理论值,实际值(面试官给的),公式都用理论值。
p真实的失误率公式。
2.4 得到K个哈希函数
怎么得到k个哈希函数,又要求彼此独立。
比如凑13个哈希函数,用两个哈希函数(f和g)就够用了。
f哈希函数返回值+1g哈希函数返回值->第一个哈希函数
f哈希函数返回值+2g哈希函数返回值->第二个哈希函数
可以构造无数个哈希函数并且彼此独立。
2.5 某产品用到此过滤器
hafs系统用得到这个,做文件分布式系统,很多小文件,通过哈希把数据分配到小文件,小文件有值或有重复的值。hafs会把每个文件分别建立小过滤器,看看str哪些小文件里面描黑,如果是白的肯定不存在记录,描黑的估计有,把描黑的都搜索一遍。
3 一致性哈希问题
分布式存储结构最常见结构:一致性哈希
解决:数据分布式存储方案->设计key多一些
关键技术:
- 哈希域变成环的设计
- 虚拟节点技术
一致性哈希没有了除余的过程,都是裸哈希值
3.1 情况
3台机器[7哈希值,100亿哈希值,2000亿哈希值]分别对应a,b,c三台机器。
算abc哈希值,算出10以后,找最近的在有序表找>=10最左的位置,属于b机器。
算出3000亿,归属于a。
归属知道后,有两个问题:
一开始没办法做到均分。
突然加一个减一个机器就不均方了,这就解决不了负载均衡问题。
3.2 虚拟节点技术,按比例抢环
m1,m2,m3三台机器,用人为(字符串)分配的方案分配给这三台机器。
比如给m1分配到1000个字符串…
用3000个字符串,用3000个虚拟节点去抢环位置,抢到以后归给自己机器。
4000亿以上数据才可能出现哈希碰撞。
两个虚拟节点给数据或迁移数据,只用在后台写虚拟节点怎么迁移对应到时机就可以了。
m4,给它新1000去夺数据,依然均衡。
虚拟节点只用量控制就行。
下线机器,也能按比例分。
例子:香水喷到屋子里,不同香水味占比不同,去掉一种香水味,剩下香水也是均匀占比。
一些问题:
m1性能好,m2和m3性能一般
就给m1分配2000个,给m2 m3各分配1000个
负载均衡和辅助管理都能做到
虚拟节点数量,就是抢到的实际数据量
3.3 亚马逊物流仓库
一个人在北京,怎么设计这个人在方圆5公里内,这些店推荐到手机上,这些店是存在的。
3.3.1 解决
整个城市认为是一个大网格,每一公里画一个方块
,以天安门为中心扩散出去,给北京经度纬度都在一个方块上,每一个网格可以得到行号和列号。
把每个商家数据库(名称、经纬度等等),再加一个数据,属于哪个格子,这个格子编号是什么,行号和列号,作为数据库字段就行。
pic1
如果人在某个经纬度,也可以定位一个格子,可以找到附近的格子,可以直接生成附近的格子
pic2
4 并行算法
4.1 岛问题
一个只有0和1两种数字的二维矩阵中,上下左右能练成一片的1,算一个岛返回矩阵中,一共有几个岛。
4.1.1 单内存单CPU
代码:时间复杂度O(N*M),已经很简洁了
def countIslands1(m):
if m is None or m[0] is None:
return 0
N = len(m) # 行
M = len(m[0]) # 列
res = 0
# 感染行为后,1变2,有多少个不连成1片的1去感染,就是岛的数量
for i in range(N): # 每一行每一列,每个位置都遍历
for j in range(M):
if m[i][j] == 1: # 这个位置如果是1,连城
res += 1 # 增加一个岛的数量
infect(m,i,j,N,M) # 连成一篇的1都会变成2,遇到感染的点会跳过
return res
# 感染函数infect
# 来到 ij位置,如果它是1,把它上下左右所有的路,1所到之处全部变成2
# N表示有多少行(固定)
# M表示有多少列(固定)
def infect(m,i,j,N,M): # 从ij出发,所有连成一片的1全变成2
# ij如果越界 或 此位置是非1,直接返回
if i < 0 or i >= N or j < 0 or j <= M or m[i][j] != 1:
return
m[i][j] = 2
# 上下左右去感染
infect(m,i + 1,j,N,M)
infect(m,i - 1,j,N,M)
infect(m,i,j + 1,N,M)
infect(m,i,j - 1,N,M)
主流程会碰一次位置,1变成2碰一次位置,邻居调用碰4次
但是大图,中国地图,01表,所以需要用并行算法
4.2 并行算法解释
4.2.1 打断
要用到并查集算法,并查集用法是查某两个事物,合成一个集合都是O(1).
这个大图岛的数量一共是一个。
这么大的图,一个cpu跑不了,需要用两个cpu,所以中间要分割图。
分割开以后只能产生更多的岛,不可能减少。
4.2.2 处理打断
在感染过程中,一方面要求岛的数量,另一方面要把自己右边界的1,是哪个感染的感染来的,记下来。
行号和列号可以表示一个位置。
看看有没有右边界处变2的点,有点就记录一下,记录为这是A感染点感染到的。
第二块cpu记录左边界感染来的
跑完后,左边两个岛,右边两个岛,边界的信息全有。
一共4个感染点ABCD,都是一个独立集合。
关注边界处左边是感染点右边是感染点的地方。
A和C应该合在一块,所以岛数-1,集合{AC}
B和C应该在一块,岛数-1,集合{ABC}
B和D连在一起,岛-1,{ABCD}
A和D已经连在一起了,岛数不-1,整体就1个岛了。
合并用并查集算法
先16个cpu,然后两两合并就变成8个cpu,再合并变成4个cpu,2,1个cpu
每次合并都把边界信息向下传递,并查集信息留着,所有区域共用一个并查集。
某两块cpu和的适合用公共cpu,每个岛的位置必然不一样,并查集都不用做线程安全。
5 资源限制技巧汇总(已全)
- 布隆过滤器用于集合的建立与查询,并可以节省大量空间;
- 一致性哈希解决数据服务器的负载管理问题;
- 利用并查集结构做岛问题的并行计算;
- 哈希函数可以把数据按照种类均匀分流;
- 位图解决某一范围上数字的出现情况,并可以节省大量空间,因为一个比特表示一个数;
- 利用分段统计思想、并进一步节省大量空间;
- 利用堆、外排序来做多个处理单元的结果合并
6 资源限制题目
6.1 题目1
32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,可以使用最多1GB的内存,怎么找到出现次数最多的数?
看标题2的位图
6.2 题目2
32位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。
可以使用最多1GB的内存,怎么找到所有未出现过的数?【进阶】
内存限制为10MB,但是只用找到一个没出现过的数即可
内存限制为3KB,但是只用找到一个没出现过的数即可
内存限制为三个变量,但是只用找到一个没出现过的数即可
思路:
42亿的量,每一个无符号整数是4字节,需要(10亿字节空间是1G)16G空间。
但是只用1G的空间,就用位图算法。
2的32次方是比特数,0表示没出现过,1表示出现过。最多需要2的32次方比特,就能知道0~2的32次方-1范围内的数出现过没有。
哈希表是4个字节表示一个数出现没出现过,位图是1个比特表示一个数出现没有出现过。
16G/32 = 500多M,够用了。
把每次放一个整数的哈希表替换成比特数组,表示任何一个数字出现没出现过。
6.2.1 内存限制为10MB
内存限制为10MB位图也做不了了。
3kb都做成整型数组能做多大?
整型数组一个4字节,3kb/4=3*1024/4 = 768
找离768最近的2的某次方,是512,是2的9次方
数组定位512长度,[512] 一定不会爆
counts[512],counts数组有512空间
整个数的范围是0~2的32次方-1,总共有2的32次方这么多数字,数组有512份,2的32次方一定能被均分为512份,刻意找的2的某次方是512,2的32/2的9 必能均分。
均分一个数是多少2的32 / 512 = 8388608
每一份8388608这么多数字。
第0份去统计0~8388608这个范围上数字出现了几个。
让第2份去统计8388609~8388609*2这个范围上数字出现了几个。
遍历所有的数组,哪一个数它中了哪个范围,就在counts数组里++。
num/8388608 = 哪一份,在第i个份,counts[i]++,统计词频。
比如遍历40亿个数,如果这个数是50515,在0~8388608这个范围,50515/8388608 = 0,所以在第0份,那么counts[0]++。
既然遍历40亿个数,那么数字肯定不够42亿,必有某一份数字量会少,此范围必缺数字。
找到那个范围L…R,继续用3kb分出512份,一直分,直到分到某个范围可以用3kb做所有比特类型的数组,统计哪个数出没出现过。
思路,给一个空间,要把它分成片
6.2.2 内存限制为三个变量
用二分查找,mid在某个位置
看看左右侧词频,左边词频是2的31次方,右侧也是
遍历40亿个数
左侧右侧必有不满,左侧超过2的31次方,右侧肯定不满
M+1 = L,mid再遍历文件,必有不满,分下去就找到了
方法不马上,最多二分32次搞定
假设右侧不够2的31次方
必然有一侧不够2的30次方
6.3 题目3
32位无符号整数的范围是0~4294967295,现在有40亿个无符号整数,可以使用最多1GB的内存,找出所有出现了两次的数。
思路:将两个比特位表示数字出现过没有,出现过0次,为00
出现过1次,为01
出现过2次,为10
出现过3次,为11
大于3次全是11,所以题目也不能查出现4次。