哈希小节

哈希小结

定义

​ 也叫做散列,把任意长度的输入通过哈希算法(散列算法)变换成固定长度的输出,输出称为哈希值(散列值)。这种转换实际上是一种压缩映射,因为散列值的空间通常来说要远小于输入空间,这样不同的输入可能会散列成相同的输出,从而造成哈希冲突,但同时也不能保证根据散列值来推断输入。

应用
  • 快速检索:需要在一个集合中快速检索是否存在某个数时,可以将哈希值作为索引,并在相应的地址中存储输入值,但需要注意冲突问题。

可以认为哈希的平均复杂度为 O ( 1 ) O(1) O(1)

  • 安全加密:将明文做哈希处理后得到的散列值,作为加密后的密码或校验值等,目前常用的哈希函数有MD5、SHA等。由于一个哈希值可以对应无数个明文,因此该过程是不可逆的。

这里有个问题,如果数据库存储的是MD5加密后的密码,那么理论上并不需要知道明文密码是什么,只需要找到一串字符可以得到相同的散列值就可以登录。

哈希函数

常用的哈希函加密数通常需要满足以下5点:

  1. 确定性,即对于相同的输入应该有确定的散列值。
  2. 快速的,即能够快速计算得到散列值。
  3. 不可逆的,即除非尝试所有可能的消息,否则无法从散列值生成消息。
  4. 雪崩性,即对输入很小的改变能引起散列值巨大的变化,使新散列值看起来和旧的毫不相干。
  5. 抗冲突性,即找到具有两个相同散列值的两个不同消息是不可行的。

前两条哈希函数都满足,关键是后三条,尤其是3和5的平衡。因为要满足3的话,输入空间应该要远大于散列空间(如对100取模,那么散列值为1可能的输入就有1,101,201…),这样的话容易产生冲突,即容易找到两个散列值相同的不同消息。因此,结合5的话可以知道散列空间不能太小。

详细可以参见关于密码学中的hash:https://zhuanlan.zhihu.com/p/44544072

常用的哈希函数有如下几种(用于哈希存储,假设需要存储的值为 k e y key key,哈希值是存储地址的索引):

  1. 直接寻址法:直接用 k e y key key值或者 k e y key key的线性函数,即 H ( k e y ) = k e y H(key)=key H(key)=key H ( k e y ) = a ∗ k e y + b H(key)=a*key+b H(key)=akey+b
  2. 除留余数法:取 k e y key key被某个不大于散列表长度 m m m的数 p p p除得到的余数作为哈希值,即 H ( k e y ) = k e y   M O D   p ,   p < < m H(key)=key\ MOD\ p,\ p<<m H(key)=key MOD p, p<<m。其中 p p p的取值非常重要,一般取素数(理由见下面),若没有取好,很容易产生冲突。
  3. 随机数法:将 k e y key key值作为随机函数的 s e e d seed seed值,随机函数的结果作为散列值。
  4. 平方取中法:将 k e y key key平方后取中间几位作为散列值。
  5. 数字分析法:取 k e y key key中比较随机的几位作为散列值,如身份证中前几位很多人都相同,那么可以取最后几位作为散列值。
冲突解决

由于一般情况下哈希函数的输入空间远远大于散列空间,设计良好的哈希函数虽然能够尽力避免冲突,但产生冲突还是难以避免,针对冲突的解决方案主要有如下几种:

  1. 开放地址法:若某个地址产生冲突,就去寻找散列空间中的下一个地址,直到不产生冲突。

例:假设关键字集合为 { 12 , 67 , 56 , 16 , 25 , 37 , 22 , 29 , 15 , 47 , 48 , 42 } \{12,67,56,16,25,37,22,29,15,47,48,42\} {12,67,56,16,25,37,22,29,15,47,48,42},采用除留余数法 p = 12 p=12 p=12,则产生的散列表如下:

下标01234567891011
k e y key key12253715162967562247

可以看到 k e y = 37 key=37 key=37时产生了冲突。

  1. 再散列法:采用两种或以上的散列函数,若某一种冲突则换一种重新散列。
  2. 链地址法:对于产生冲突的 k e y key key在一条链表中串联起来,相应的检索也在对应散列值的链表中完成。
为什么对关键字取模一般用质数

如果输入是独立随机均匀的,那么 p p p取任意值都是一样的。但现实情况中,往往不满足均匀这一条件,关键字之间往往是有联系的。
看如下这种情况:
k e y = { 6 , 12 , 28 , 24 , 30 , 36... } key=\{6,12,28,24,30,36...\} key={6,12,28,24,30,36...},若取 p = 10 p=10 p=10,那么毫无疑问$hash(key)\in{0,2,4,6,8} , 没 有 映 射 到 ,没有映射到 {1,3,5,7,9} , 这 样 就 加 大 了 冲 突 的 可 能 性 。 分 析 : 由 于 ,这样就加大了冲突的可能性。 分析:由于 hash(key)=key%p , 假 设 ,假设 key/p=a , 则 ,则 key=ap+hash(key) , 得 ,得 hash(key)=key-ap 。 将 式 子 变 形 可 得 。将式子变形可得 hash(key)=gcd(key,p)(\frac{key}{gcd(key,p)}-\frac{ap}{gcd(key,p)}) 。 因 此 , 。因此, hash(key) 的 值 是 的值是 key 和 和 p 的 最 大 公 约 数 的 倍 数 。 由 于 上 述 中 的 的最大公约数的倍数。由于上述中的 key 都 是 6 的 倍 数 , 因 此 都是6的倍数,因此 6hash(key) 都 是 都是 gcd(6,10)=2 的 倍 数 。 若 的倍数。若 p$选为素数则不会出现这种情况。

参见知乎:

  • https://www.zhihu.com/question/20806796
  • http://www.vvbin.com/?p=376

好的素数取值可以参见:https://planetmath.org/goodhashtableprimes

简单代码实现

哈希函数使用除留余数法,解决碰撞冲突采用链地址法。

class MyHashMap {
private:
    vector<list<pair<int, int>>> data;
    static const int base = 769;
    static int hash(int key) {
        return key % base;
    }
public:
    /** Initialize your data structure here. */
    MyHashMap(): data(base) {}
    
    /** value will always be non-negative. */
    void put(int key, int value) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                (*it).second = value;
                return;
            }
        }
        data[h].push_back(make_pair(key, value));
    }
    
    /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
    int get(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                return (*it).second;
            }
        }
        return -1;
    }
    
    /** Removes the mapping of the specified value key if this map contains a mapping for the key */
    void remove(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                data[h].erase(it);
                return;
            }
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值