哈希表&一致性哈希&超有爱的并查集

本文深入探讨了哈希函数、哈希表、布隆过滤器、RandomPool结构设计、一致性哈希算法、并查集及其应用等内容,覆盖了数据结构与算法的重要知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 哈希函数,哈希表与布隆过滤器

  链接 : https://blog.csdn.net/weixin_35479108/article/details/88830850 中,我总结了有关于信息指纹,哈希表,布隆过滤器,位数组的内容。
  补充一点内容,有关哈希函数,如果急需要1000个哈希函数,但是要求哈希函数相互独立,应该怎么实现?这个可以用1个哈希函数改出来,假设一个哈希函数 h h h映射之后对应的是一个16字节的输出,这16个字节肯定是相互独立的,这时可以用前8个字节作为 h 1 h_1 h1,后8个字节作为 h 2 h_2 h2,剩下的 h 3 = h 1 + h 2 , h 4 = h 1 + 2 ∗ h 2 ⋯ h_3 = h_1+h_2,h_4=h_1+2*h_2\quad \cdots h3=h1+h2,h4=h1+2h2。视频里介绍16个字节每一位都是相互独立的,最后生成多少个哈希函数都是独立。(我其实没有太想明白,了解的可以教我一下~)

2. 设计RandomPool结构

【题目】 设计一种结构,在该结构中有如下三个功能:
   insert(key):将某个key加入到该结构,做到不重复加入
   delete(key):将原本在结构中的某个key移除。
   getRandom(): 等概率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是 O(1)
  哈希表的性质是可以根据哈希函数基本均匀的映射到输出域中,但不是严格等概率的,如果要严格等概率返回哈希表中的值,肯定要做一些处理,改造哈希函数的话是很麻烦的,所以可以考虑利用哈希表的性质来实现。
  首先,准备两张哈希表,以及一个变量size,记录哈希表中数据的多少,其中一张哈希表key存储字符串,value存储size,另一张哈希表key存储size,value存储字符串,假设存储的字符是英文字母,则效果如下图:

在这里插入图片描述

  这时insert数据很简单,直接同时更新两个map并且size++就可以了。
  如果要取数据为什么就是等概率了呢,取数据的时候,我们可以根据size的值等概率的生成0~size-1的数字,根据生成的这个数字,我们在map2中取数据一定是等概率的。
  但是delete数据的时候,根据插入数据时size大小生成的index就会产生漏洞,size也不代表map的大小了。此时如果getRandom()会发生根据size等概率生成的数据取值发现没有数据的情况,getRandom就不是O(1)了。那么应该如何进行操作呢?最简单的方法就是当产生漏洞的时候,同时调整两张map成为没有漏洞的情况不就可以了。也就是只要产生漏洞,就使用最后一条记录填充漏洞,index是当前漏洞的index,同时删除最后一条记录就OK了。

#include <iostream>
#include <hash_map>
#include <string>
#include <random>
#include <time.h>

using namespace __gnu_cxx;
using namespace std;

struct hash_string{
        size_t operator()(const string& str) const
        {
                return __stl_hash_string(str.c_str());
        }
};

struct compare_str
{
    bool operator() (const string &str1,const string &str2) const
    {
        return str1 == str2;
    }
};


class RandomPool{
public:
    hash_map<string,int,hash_string,compare_str> map1;
    hash_map<int,string> map2;
    int length;

    RandomPool()
    {
        length = 0;
    }

    void initMap()
    {
        char ch [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        string str;
        for(int i=0;i < 26;i++)
        {
            str = ch[i];
            map1[str] = i;
            map2[i] = str;
            length++;
        }
    }

    void insert(string str)
    {
        if(!map1[str])
        {
            map1[str] = length;
            map2[length] = str;
            length++;
        }
    }

    string getRandom()
    {
        default_random_engine e;
        e.seed(100);//100为随机种子
        uniform_real_distribution<double> u(0, 1); //随机数分布对象
        double random = u(e);
        int rand = int(random*(length+1));///产生0-ize-1的随机数
        string str = map2[rand];
        return str;
    }

    void delete1(string str)
    {
        if(!map1[str])
        {
            return;
        }
        else
        {
            int index = map1[str];
            map1.erase(str);
            map2.erase(index);
            string str1 = map2[length];
            map1[str1] = index;
            map2[index] = str1;
            length--;
        }
    }

    template<class T>
    void printMap(T &map1)
    {
        auto it = map1.begin();
        while(it != map1.end())
        {
            cout<< "key = "<< it->first <<"         value = "<< it->second <<endl;
            ++it;
        }
    }
};

int main()
{
    cout << "Hello world!" << endl;
    /**
    为什么创建的两个map传入的参数不一样,请参考
    http://www.cnblogs.com/waytofall/archive/2012/06/04/2534386.html
    **/
    RandomPool rp;
    rp.initMap();
    cout<<rp.length<<endl;
    cout<<rp.getRandom();
    rp.delete1("E");
    cout<<rp.length<<endl;
    cout<<rp.getRandom();
    if(rp.map1["key"])
        cout<<"hello"<<endl;
    return 0;
}

3. 一致性哈希

  链接:https://www.cnblogs.com/zhzhang/p/3740489.html 是哈希表的原理与实现,其中一致性哈希讲解的很清楚,也介绍了一些场景应用。
  一致性哈希设计到服务器的设计部署问题,假设现在有一个问题,有一个文件又100T,里面存储着字符串,这时肯定想着不能直接一台服务器干啊,那不得累死牛啊。假设我们有1000台服务器,这时将每一行的字符串读出,进行哈希映射,得到哈希code,hashcode%1000,得到结果就对应每一台服务器,因为对每个字符串的映射一定得到一样的哈希code,所以一样的字符串一定会分配到一样的机器上,这样并行处理就会快很多。
  但是这时有一些情况:

  1. 如果突然一台服务器坏掉了,那么分配给该机器的所有对象都需要重新分配,为了给每台机器分配均匀的对象就需要所有的对象重新分配
  2. 如果数据增加了很多,需要增加一些服务器进行处理,也需要所有对象重新分配

  1 和 2 意味着什么?这意味着突然之间几乎所有的 cache 都失效了。对于服务器而言,这是一场灾难,洪水般的访问都会直接冲向后台服务器;

  再来考虑第三个问题,由于硬件能力越来越强,你可能想让后面添加的节点多做点活,显然上面的 hash 算法也做不到。有什么方法可以改变这个状况呢,这就是 consistent hashing 一致性 hash 算法…

hash 算法和单调性

  Hash 算法的一个衡量指标是单调性( Monotonicity ),定义如下:

  单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。

  容易看到,上面的简单 hash 算法 hash(object)%N 难以满足单调性要求。

consistent hashing 算法的原理

  consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 服务器时,它能够尽可能小的改变已存在 key 映射关系,尽可能的满足单调性的要求。

  下面就来按照 5 个步骤简单讲讲 consistent hashing 算法的基本原理。

1. 环形hash 空间

  考虑通常的 hash 算法都是将 value 映射到一个 32 位的 key 值,也即是 0 − 2 32 − 1 0-2^{32}-1 02321 次方的数值空间;我们可以将这个空间想象成一个首( 0 )尾( 2 32 − 1 2^{32}-1 2321)相接的圆环,如下图所示的那样。

在这里插入图片描述

2. 把对象映射到hash 空间

  接下来考虑 4 个对象 object1~object4 ,通过 hash 函数计算出的 hash 值 key 在环上的分布如下图所示。

在这里插入图片描述
4 个对象的 key 值分布

1	1   hash(object1) = key1;
2	2   … …
3	3   hash(object4) = key4;

3. 把 服务器映射到hash 空间
  Consistent hashing 的基本思想就是将对象和 服务器都映射到同一个 hash 数值空间中,并且使用相同的 hash 算法。假设当前有 A,B