左神算法第五节课:认识哈希函数和哈希表,设计RandomPool结构,布隆过滤器,一致性哈希,岛问题,并查集结构
认识哈希函数和哈希表
MD5Hash值的返回范围:0~9+a~f,是16位,故范围是0~16^16(2^64)-1,
【Hash函数】,又叫散列函数;
Hash的性质:
1) 输入域无穷大;
2) 输出域相对固定较小;
3) 输入一样,输出一样;
4) 输入不一样,输出可能一样,也可能不一样;
5) 均匀分布在输出域中,如经典表中对17取模,使得每个桶内的长度基本一样;散列函数,如同香水在房间;Hash函数和输入的顺序无关;
第4条即是Hash碰撞的原因:多个输入对应同一个输出;这是必然会产生的,因为输入域无穷,输出域确实有穷的。
【Hash表经典结构】
0~16是17个桶,当不够用的时候,就会扩容。
【JVM中的Hash表】
设计RandomPool结构
【题目】 设计一种结构,在该结构中有如下三个功能:
insert(key):将某个key加入到该结构,做到不重复加入。
delete(key):将原本在结构中的某个key移除。
getRandom():等概率随机返回结构中的任何一个key。
【要求】Insert、delete和getRandom方法的时间复杂度都是O(1);
分析:由于复杂度要求,使得不能遍历;使用两张表,map1和map2,并使用一个计数变量size,将map1存入为(str, size),map2为(size, str);增加同基本,删除的话要注意删除时避免间断的出现,解决间断问题,
1 import java.util.HashMap;
2
3 public class Code_02_RandomPool {
4 public static class Pool<K> {
5 private HashMap<K, Integer> keyIndexMap;
6 private HashMap<Integer, K> indexKeyMap;
7 private int size;
8
9 public Pool() {
10 keyIndexMap = new HashMap<>();
11 indexKeyMap = new HashMap<>();
12 size = 0;
13 }
14
15 public void add(K k) {
16 keyIndexMap.put(k, size);
17 indexKeyMap.put(size, k);
18 size++;
19 }
20 public void insert(K k) {
21 if (!keyIndexMap.containsKey(k)) {
22 keyIndexMap.put(k, size);
23 indexKeyMap.put(size++, k);
24 }
25 }
26 public void delete(K key) {
27 //要保证删除之后,没有间断
28 if (keyIndexMap.containsKey(key)) {
29 int deleteIndex = keyIndexMap.get(key);
30 int lastIndex = --size;
31 K lastKey = indexKeyMap.get(lastIndex);
32 keyIndexMap.put(lastKey, deleteIndex);
33 indexKeyMap.put(deleteIndex, lastKey);
34 keyIndexMap.remove(key);
35 indexKeyMap.remove(lastKey);
36 }
37 }
38
39 public K getRandom() {
40 if (size == 0) {
41 return null;
42 }
43 int index = (int)(Math.random()*size);
44 return indexKeyMap.get(index);
45 }
46
47 }
48 private static void test() {
49 Pool<String> pool = new Pool<String>();
50 pool.insert("zuo");
51 pool.insert("cheng");
52 pool.insert("yun");
53 System.out.println(pool.size);
54 System.out.println(pool.getRandom());
55 System.out.println(pool.getRandom());
56 System.out.println(pool.getRandom());
57 System.out.println(pool.getRandom());
58 System.out.println(pool.getRandom());
59 System.out.println(pool.getRandom());
60
61 }
62 public static void main(String[] args) {
63 test();
64 }
65 }
删去任一个元素时,需要拿最后一个元素来填充该位置。故需要取得最后一个元素的索引。
LastIndex = --size;
再存储最后一个元素的str值,
lastKey = indexKeyMap.get(lastindex);
认识布隆过滤器
布隆过滤器:
- 而布隆过滤器只需要哈希表1/8到1/4的大小就能解决同样的问题,它实际上是一个很长的二进制向量和一系列的随机映射函数。
- 如果一个优秀的函数能够做到不同的输入值所得到的返回值可以均匀的分布在输出域S中,将其返回值对m取余(%m),得到的返回值可以认为也会均匀的分布在0~m-1位置上。
1)isSameSet(A,B),A∈Set1,B∈Set2。查某个东西是否在一个集合中,存在一定的失误率(宁可错杀三千,不可放过一个)。并且使用很少的内存。由集群到单机。
如何做出0~m-1的bit空间,可以找一个int[],一个int是4字节,即32bit;一个整数表示32bit;
1 package class_05;
2 /*
3 * 怎么做出长度为0-m-1的bit类型数组,拿基础类型来拼;
4 */
5 public class BitTest {
6 public static void main(String[] args) {
7 int[] arr = new int[1000];//1000*32=32000bit
8 int singleIntIsBit = 32;
9 int index = 30000;
10 int intIndex = index/singleIntIsBit;
11 int bitIndex = index%singleIntIsBit;
12 System.out.println(intIndex+" "+bitIndex);
13 //(1<<bitIndex)是将第intIndex桶内的第bitIndex位上的0变为1;
14 arr[intIndex] = arr[intIndex] | (1<<bitIndex);
15 }
16 }
一个URL经过k个不同的hash函数,得到的哈希值再模上m,得到的位置上给涂黑。
给定一个URL如果经过k此比对都是黑的,那么说明是黑名单,如果有一个不是黑的,就说明不在黑名单之内。
M的长度与失误率有关,m越大,失误率越小。
布隆过滤器的大小m公式:
m是bit的长度,n是样本量,P预期失误率。
比如:n=100亿,P=0.0001(万分之一),m=131,571,428,572bit=131,571,428,572/8字节=22.3G,22.3G为实际空间。
Hash函数的个数K:
可得K= 0.7*m/n=13个,那么真实失误率p:
p=6/十万;
在比如:
假设我们的缓存系统,key为userId,value为user。如果我们有10亿个用户,规定失误率不能超过0.01%,通过计算器计算可得m=19.17n,向上取整为20n,也就是需要200亿个bit,换算之后所需内存大小就是2.3G。通过第二个公式可计算出所需哈希函数k=14.因为在计算m的时候用了向上取整,所以真是的误判率绝对小于等于0.01%。
关于BloomFilter,实际使用时不需要我们自己实现,谷歌已经帮我们实现好了,直接引入依赖就好。
- pom依赖:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
- 核心API
1 /**
2 * Returns {@code true} if the element <i>might</i> have been put in this Bloom filter,
3 * {@code false} if this is <i>definitely</i> not the case.
4 */
5 public boolean mightContain(T object) {
6 return strategy.mightContain(object, funnel, numHashFunctions, bits);
7 }
- 小例子
1 public static void main(String... args){
2 /**
3 * 创建一个插入对象为一亿,误报率为0.01%的布隆过滤器
4 */
5 BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 100000000, 0.0001);
6 bloomFilter.put("121");
7 bloomFilter.put("122");
8 bloomFilter.put("123");
9 System.out.println(bloomFilter.mightContain("121"));
10 }
2)union(A,B),A∈Set1,B∈Set2,==》Set。较少元素的集合挂在较多元素的集合下面,
优化之处:在查某个节点的代表节点时,比如1-2-3-4-5,1-6,当查找4的代表节点时,需要把2,3,4都直接关联到1节点,如1-2,1-3,1-4-5,1-6,这样的话,以后再查6就快多了。
结论:假设有N个样本,查询次数+合并次数,整体逼近O(N)及以上,那么,单次操作(不管查询还是合并)平均时间复杂度都是O(1)。
认识一致性哈希
一致性哈希结构可以把数据迁移的代价变得很低,
HashCode值为0~2^64;
5台机器,m1~m5;
五台机器的HashCode分别为mc1~mc5;
排序后形成数组mc=[mc3,mc4,mc5,mc2,mc1];
前端所有抗压的每个都有一个mc数组,然后,二分查找找出左边第一个比自己大的位置。
在图中就是求出zuo的hash值后,顺时针从m3开始查找,找到第一个比zuo的hash的、值大的机器,然后就将zuo放入该机器存储。
岛问题
一个矩阵中只有0和1两种值,每个位置都可以和自己的上、下、左、右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛?
举例:
0 0 1 0 1 0
1 1 1 0 1 0
1 0 0 1 0 0
0 0 0 0 0 0
这个矩阵中有三个岛。
思路:单机
1 public class Code_03_Islands {
2
3 public static int countIslands(int[][] m) {
4 if (m == null || m[0] == null) {
5 return 0;
6 }
7 int res = 0;
8 int N = m.length;
9 int M = m[0].length;
10 for (int i = 0; i < N; i++) {
11 for (int j = 0; j < M; j++) {
12 if (m[i][j] == 1) {
13 res++;
14 infect(m, i, j, N, M);
15 }
16 }
17 }
18 return res;
19 }
20
21 private static void infect(int[][] m, int i, int j, int N, int M) {
22 if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1) {
23 return;
24 }
25 m[i][j] = 2;
26 infect(m, i - 1, j, N, M);// 上
27 infect(m, i, j + 1, N, M);// 右
28 infect(m, i + 1, j, N, M);// 下
29 infect(m, i, j - 1, N, M);// 左
30 }
31
32 public static void main(String[] args) {
33 test();
34
35 }
36
37 private static void test() {
38 int[][] m1 = {
39 { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
40 { 0, 1, 1, 1, 0, 1, 1, 1, 0 },
41 { 0, 1, 1, 1, 0, 0, 0, 1, 0 },
42 { 0, 1, 1, 0, 0, 0, 0, 0, 0 },
43 { 0, 0, 0, 0, 0, 1, 1, 0, 0 },
44 { 0, 0, 0, 0, 1, 1, 1, 0, 0 },
45 { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, };
46 System.out.println(countIslands(m1));
47
48 int[][] m2 = {
49 { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
50 { 0, 1, 1, 1, 1, 1, 1, 1, 0 },
51 { 0, 1, 1, 1, 0, 0, 0, 1, 0 },
52 { 0, 1, 1, 0, 0, 0, 1, 1, 0 },
53 { 0, 0, 0, 0, 0, 1, 1, 0, 1 },
54 { 0, 0, 0, 0, 1, 1, 1, 0, 0 },
55 { 0, 0, 0, 0, 0, 0, 0, 0, 1 }, };
56 System.out.println(countIslands(m2));
57
58 }
59
60 }
分布式:
用到了union(A,B),边界问题。
图1. 整体 图2. 边界
图3. 并行结构,多边界
并查集结构
一个矩阵分到两个计算节点上,先遍历各个节点内的子矩阵,找出有多少个岛,以及各岛的头节点,可以发现一共有4个岛,各边界的头结点如箭头所示,当比较第一行的边界时,先判断A和C是否是在同一个集合,如果不是,那么岛的数量4-1=3,并且将AC合并,然后比较第二行发现AC已经同属一个集合,故岛的数量不变,继续比较,第三行左边为0,进行第四行比较,发现BC不是在同一个集合岛的数量3-1=2。以此进行比较,直至最后所有边界都比较完毕。
1 package class_05;
2
3 import java.util.HashMap;
4 import java.util.List;
5 import java.util.Stack;
6
7 public class Code_04_UnionFind {
8
9 public static class Node {
10 // whatever you like String,Int,char...
11 }
12
13 public static class UnionFindSet {
14 public HashMap<Node, Node> fatherMap;//key:child,value:father
15 public HashMap<Node, Integer> sizeMap;//该点所在集合一共有多少节点,谁多并入谁;
16
17 public UnionFindSet(List<Node> nodes) {
18 makeSet(nodes);
19 }
20
21 private void makeSet(List<Node> nodes) {
22 fatherMap = new HashMap<>();
23 sizeMap = new HashMap<>();
24 for (Node node : nodes) {
25 fatherMap.put(node, node);//第一次,每一个node自己形成一个集合,故父节点是其本身;
26 sizeMap.put(node, 1);//第一次,各自集合中只有自己一个元素;
27 }
28 }
29 /*
30 * 找到head,并且将node到head路径上的nodes的父节点都指向head;
31 * 递归寻找至根节点,并返回根节点
32 */
33 public Node findHead(Node node) {
34 Node father = fatherMap.get(node);
35 if (father != node) {
36 father = findHead(father);
37 }
38 fatherMap.put(node, father);
39 return father;
40 }
41 //非递归,并返回根节点
42 public Node findHead2(Node node) {
43 Stack<Node> stack = new Stack<>();
44 Node father = fatherMap.get(node);
45 while (father != node) {
46 stack.push(node);
47 node = father;
48 father = fatherMap.get(node);
49 }
50 //将路径上每个节点都指向根节点
51 while (!stack.isEmpty()) {
52 fatherMap.put(stack.pop(),father);
53 }
54 return father;
55 }
56
57 /*两个功能:
58 * 1.isSameSet(Node a,Node b);
59 * 2.union(Node a, Node b);
60 */
61 public boolean isSameSet(Node a, Node b) {
62 return findHead(a) == findHead(b);
63 }
64
65 public void union(Node a, Node b) {
66 if (a == null || b == null) {
67 return;
68 }
69 Node aHead = findHead(a);
70 Node bHead = findHead(b);
71 if (aHead != bHead) {
72 int aSetSize = sizeMap.get(aHead);//通过根节点来求出size,其他节点的信息是错的;
73 int bSetSize = sizeMap.get(bHead);
74 if (aSetSize > bSetSize) {
75 //将b集合合并入a集合内;
76 fatherMap.put(bHead, aHead);
77 sizeMap.put(aHead, aSetSize+bSetSize);//只用改根节点的大小即可,与上方利用根节点求size呼应;
78 }else {
79 //将a集合合并入b集合内;
80 fatherMap.put(aHead, bHead);
81 sizeMap.put(bHead, aSetSize+bSetSize);
82 }
83 }
84 }
85 }
86
87 public static void main(String[] args) {
88 // TODO Auto-generated method stub
89
90 }
91
92 }
左神算法第五节课:认识哈希函数和哈希表,设计RandomPool结构,布隆过滤器,一致性哈希,岛问题,并查集结构的更多相关文章
- centos shell脚本编程1 正则 shell脚本结构 read命令 date命令的用法 shell中的逻辑判断 if 判断文件、目录属性 shell数组简单用法 $( ) 和${ } 和$(( )) 与 sh -n sh -x sh -v 第三十五节课
centos shell脚本编程1 正则 shell脚本结构 read命令 date命令的用法 shell中的逻辑判断 if 判断文件.目录属性 shell数组简单用法 $( ) 和$ ...
- centos 特殊权限 各种搜索命令 lsattr ,chattr,suid,sgid,sbit,file,type是否是内置命令,stat文件属性 ,whereis,locate,find,ln 内部命令和外部命令 第五节课
centos 特殊权限 各种搜索命令 lsattr ,chattr,suid,sgid,sbit,file,type是否是内置命令,stat文件属性 ,whereis,locate,find,ln ...
- centos lamp/lnmp阶段复习 以后搬迁discuz论坛不需要重新安装,只需修改配置文件即可 安装wordpress 安装phpmyadmin 定时备份mysql两种方法 第二十五节课
centos lamp/lnmp阶段复习 以后搬迁discuz论坛不需要重新安装,只需修改配置文件即可 安装wordpress 安装phpmyadmin 定时备份mysql两种方法 第二十五节 ...
- centos Linux系统日常管理2 tcpdump,tshark,selinux,strings命令, iptables ,crontab,TCP,UDP,ICMP,FTP网络知识 第十五节课
centos Linux系统日常管理2 tcpdump,tshark,selinux,strings命令, iptables ,crontab,TCP,UDP,ICMP,FTP网络知识 第十五节课 ...
- 风炫安全web安全学习第三十五节课 文件下载和文件读取漏洞
风炫安全web安全学习第三十五节课 文件下载和文件读取漏洞 0x03 任意文件下载漏洞 一些网站由于业务需求,往往需要提供文件下载功能,但若对用户下载的文件不做限制,则恶意用户就能够下载任意敏感文件, ...
- 风炫安全WEB安全学习第二十五节课 利用XSS键盘记录
风炫安全WEB安全学习第二十五节课 利用XSS键盘记录 XSS键盘记录 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源.所以xyz.com下的js脚本采用a ...
- 第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中
第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详 ...
- 左神算法第一节课:复杂度、排序(冒泡、选择、插入、归并)、小和问题和逆序对问题、对数器和递归(Master公式)
第一节课 复杂度 排序(冒泡.选择.插入.归并) 小和问题和逆序对问题 对数器 递归 1. 复杂度 认识时间复杂度常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数 ...
- 第一百七十五节,jQuery,工具函数
jQuery,工具函数 学习要点: 1.字符串操作 2.数组和对象操作 3.测试操作 4.URL 操作 5.浏览器检测 6.其他操作 工具函数是指直接依附于 jQuery 对象,针对 jQuery 对 ...
随机推荐
- Springmvc中参数的绑定
.处理器适配器在执行Handler之前需要把http请求的key/value数据绑定到Handler方法形参数上. 1.默认支持的参数类型: HttpServletRequest,HttpServle ...
- Eureka详解系列(一)--先谈谈负载均衡器
这个系列开始研究 Eureka,在此之前,先来谈谈负载均衡器. 本质上,Eureka 就是一个负载均衡器,可能有的人会说,它是一个服务注册中心,用来注册服务的,这种说法不能说错,只是有点片面. 在这篇 ...
- ASP.NET MVC--sqlserver数据库脚本的导入导出
1.右键选择数据库---任务----生成脚本 2.弹出如下框 导出整个表,默认下一步,否则选择特定数据库对象表单选框 3.修改文件名路径,可以保存脚本到制定路径,否则为默认,点击高级进入 要编写脚本的 ...
- 渗透测试中期--漏洞复现--MS08_067
靶机:Win2k3 10.10.10.130 攻击机:BT5 10.10.10.128 一:nmap 查看WinK3是否开放端口3389 开放3389方法:我的电脑->属性-&g ...
- Spring Boot(IDEA,Gradle)超详细用户管理项目(一)——Hello World
1.构建工具的配置(Gradle):自定义-所有设置:构建.执行.部署-构建工具-Gradle: 设置Gradle用户主目录:(该目录相当于仓库,gradle将下载所需依赖到此目录下),此目录下可新建 ...
- Java运算符及包机制
Java中的运算符及包机制 算术运算符:+ - * / % ++ -- 赋值运算符:=,+=,-=,*=,/= 关系运算符:>,<,>=,<=,==,!=,instanceof ...
- django 中连接mysql数据库的操作步骤
django中连接mysql数据库的操作步骤: 1 settings配置文件中 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mys ...
- CoeMonkey少儿编程第4章 变量
点击这里,现在就开启CodeMonkey的趣味编程之旅. 目标 了解什么是变量 了解变量的命名规则 掌握如何使用变量 变量 什么是变量?顾名思义,变量就是可以变化的量. 和变量相对的是常量,即不可变化 ...
- Ajax函数的封装
Ajax函数的封装 function ajax(options) { // 1 创建Ajax对象 let xhr = new XMLHttpRequest(); // 2 告诉Ajax对象要想哪儿发送 ...
- 长连接 短连接 RST报文
https://baike.baidu.com/item/短连接 短连接(short connnection)是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时,才去建立一个连接,数 ...