认识哈希函数

  1. 哈希函数的输入域是无穷大的,输出域是有限的
  2. 输入相同的值,返回结果都是一样的,没有随机成分
  3. u:if in —> same out 不同的输入也有可能有相同的输出(哈希碰撞)
  4. 它能把一定量的数据离散的放到同一个作用域里,使得它的均匀性和离散性得到保障,数据量小时效果不明显

    哈希表的实现

  5. 哈希表是基于哈希函数实现的

    设计RandomPool结构

    【题目】设计一种结构,在该结构中有如下三个功能:

  6. insert(key):将某个key加入到该结构,做到不重复加入

  7. delete(key):将原本在结构中的某个key移除
  8. getRandom():等概率随机返回结构中的任何一个key。

【要求】Insert、delete和getRandom方法的时间复杂度都是O(1)

  1. public class RandomPool {
  2. public static class Pool<K> {
  3. private HashMap<K, Integer> keyIndexMap;
  4. private HashMap<Integer, K> indexKeyMap;
  5. private int size;
  6. public Pool() {
  7. this.keyIndexMap = new HashMap<K, Integer>();
  8. this.indexKeyMap = new HashMap<Integer, K>();
  9. this.size = 0;
  10. }
  11. public void insert(K key) {
  12. if (!this.keyIndexMap.containsKey(key)) {
  13. this.keyIndexMap.put(key, this.size);
  14. this.indexKeyMap.put(this.size++, key);
  15. }
  16. }
  17. public void delete(K key) {
  18. if (this.keyIndexMap.containsKey(key)) {
  19. int deleteIndex = this.keyIndexMap.get(key);
  20. int lastIndex = --this.size;
  21. K lastKey = this.indexKeyMap.get(lastIndex);
  22. this.keyIndexMap.put(lastKey, deleteIndex);
  23. this.indexKeyMap.put(deleteIndex, lastKey);
  24. this.keyIndexMap.remove(key);
  25. this.indexKeyMap.remove(lastIndex);
  26. }
  27. }
  28. public K getRandom() {
  29. if (this.size == 0) {
  30. return null;
  31. }
  32. int randomIndex = (int) (Math.random() * this.size); // 0 ~ size -1
  33. return this.indexKeyMap.get(randomIndex);
  34. }
  35. }
  36. public static void main(String[] args) {
  37. Pool<String> pool = new Pool<String>();
  38. pool.insert("zuo");
  39. pool.insert("cheng");
  40. pool.insert("yun");
  41. System.out.println(pool.getRandom());
  42. System.out.println(pool.getRandom());
  43. System.out.println(pool.getRandom());
  44. System.out.println(pool.getRandom());
  45. System.out.println(pool.getRandom());
  46. System.out.println(pool.getRandom());
  47. }
  48. }

详解布隆过滤器

布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

首先要了解位图

public static void main(String[] args) {
        // a 32bit java中int类型占32位 
        int a = 0;

        int[] arr = new int[10]; // 32bit * 10 -> 320bits

        // arr[0] int 0 ~ 31
        // arr[1] int 32 ~ 63
        // arr[2] int 64 ~ 95

        int i = 178; // 想取得第178个bit的状态

        int numIndex = 178 / 32; // 查找178在数组中的哪位上 arr[n]
        int bitIndex = 178 % 32; // 查找178在arr[n]上的哪一位上

        // 拿到178位的状态
        // arr[numIndex] >> (bitIndex) 178位的信息跑到最右侧
        // 想找某一位就让某一位移到最右侧,然后和1与
        int s = (    (arr[numIndex] >> (bitIndex))    & 1);

        // 请把178位的状态改成1
        arr[numIndex] = arr[numIndex] | (1 << (bitIndex));

        i = 178; // 请把178位的状态改成0
        arr[numIndex] = arr[numIndex] & (~    (1 << bitIndex));

        i = 178; // 请把178位的状态拿出来

        // bit 0 1
        int bit = (arr[i / 32] >> (i % 32)) & 1;
    }

布隆过滤器就是一个位图

比如黑明单网站,将所有的黑名单网站放到位图里:
定义m大小的位图,k个哈希函数

  1. 先找几个哈希函数,然后将url放到哈希函数里将返回值对应的位的状态进行改变,所有的哈希函数返回值都在位图里进行改变为1
  2. 如果想要判断某一个url是否在位图里,就将所有的状态全部查出来,如果全部为1就在过滤器里,否则没有。

    由上面推出

  3. 黑(不合法url) ——-> 白(合法url) 不可能 因为你的状态在过滤器里面完全存在

  4. 白(合法url) ———> 黑(不合法url) 可能 如果过滤器的大小设置的很小,然后数据量很大,那么所有的状态可能都会被改过,很可能出错。
  5. 不适合删除,因为删除是改变状态,但改某一个状态可能会影响其它数据
  6. 位图很大,失误率会很小。位图很小,失误率会很大。
  7. 哈希函数的个数不能太多,也不能太少。
    1. 如果太多,一个数据就得改变很多个状态,如果数据很多,那么失误率会增加。
    2. 太少不够精准
  8. 首先根据数据量确定位图的大小,然后确定使用几个哈希函数

    设计布隆过滤器的三个公式

    如果面试时,问到有关黑名单这种问题,可以问一下面试管是否有失误率,如果有就是使用布隆过滤器

  9. n = 样本量 p = 失误率

哈希函数与哈希表 - 图1 计算布隆过滤器需要多少位,有可能计算出小数,向上取整
k = ln2 m / n % 0.7 m / n 计算需要多少个哈希函数
哈希函数与哈希表 - 图2
image.png

一致性哈希原理

image.png
image.png
在前端服务器上有一个数组,这个数组里面存的是后面存储服务器的哈希值的值,并且是按顺序来存储的,这样的在接收到一个请求的话,就很方便使用二分法查找到这个请求对应所应该对应的服务器。

上面的机器成环存在两个问题:

这两个问题都是有哈希函数的性质所引起的,哈希函数是离散的函数,是均匀分布的,但是前提是量,数量要多,对于上面的问题来说也就是服务器的数量要多,量起来之后才能体现哈希的特点。

  1. 在服务器数量较少的时候,哈希域 (也就是上图中的那个环),不一定会被服务器均分
  2. 就算你解决了第一个问题,服务器均分了哈希域,当你再增加服务器的时候,哈希域又注定不会被均分。

    上面两个问题是由哈希函数的性质引起的,那么又该如何解决上面的问题呢?

    一个方法,虚拟结点技术:
    假设环还是那个环,服务器还是那三台服务器
    M1,M2, M3
    给M1在哈希域环上虚拟出10000个结点,M2也是,M3也是,此时将有30000个结点遍布在环上,归属于M1的10000个结点中应该存放的数据,都存放在M1上。此时哈希域上的结点数量起来了,哈希域也就自然被均分了。
    第一个问题解决,当增加服务器数量的时候也是类似的方法,将服务器虚拟出大量的结点,这样就达到了均分的目的。第二个问题也被解决了
    关于服务器虚拟出来的节点类似于路由器中的路由表,可以根据路由表找到结点,也可以根据节结点知道,这个结点属于哪一个路由器。

    岛问题

    【题目】一个矩阵中只有0和1两种值,每个位置都可以和自己的上、下、左、右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛?
    【举例】

    001010 111010 100100 000000

这个矩阵中有三个岛
【进阶】如何设计一个并行算法解决这个问题

public class Islands {

    public static int countIslands(int[][] m) {
        if (m == null || m[0] == null) {
            return 0;
        }
        int N = m.length;
        int M = m[0].length;
        int res = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (m[i][j] == 1) {
                    res++;
                    infect(m, i, j, N, M);
                }
            }
        }
        return res;
    }

    public static void infect(int[][] m, int i, int j, int N, int M) {
        if (i < 0 || i >= N || j < 0 || j >= M || 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);
    }

    public static void main(String[] args) {
        int[][] m1 = {  { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
                        { 0, 1, 1, 1, 0, 1, 1, 1, 0 }, 
                        { 0, 1, 1, 1, 0, 0, 0, 1, 0 },
                        { 0, 1, 1, 0, 0, 0, 0, 0, 0 }, 
                        { 0, 0, 0, 0, 0, 1, 1, 0, 0 }, 
                        { 0, 0, 0, 0, 1, 1, 1, 0, 0 },
                        { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, };
        System.out.println(countIslands(m1));

        int[][] m2 = {  { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 
                        { 0, 1, 1, 1, 1, 1, 1, 1, 0 }, 
                        { 0, 1, 1, 1, 0, 0, 0, 1, 0 },
                        { 0, 1, 1, 0, 0, 0, 1, 1, 0 }, 
                        { 0, 0, 0, 0, 0, 1, 1, 0, 0 }, 
                        { 0, 0, 0, 0, 1, 1, 1, 0, 0 },
                        { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, };
        System.out.println(countIslands(m2));

    }
}