15.哈希表基础

哈希函数设计

  • O(1)的复杂度
  • 哈希函数的设计是很重要的,重点解决哈希冲突
  • 哈希表充分体现了算法设计领域的经典思想:空间换时间
  • 哈希表是时间和空间之间的平衡
  • “键”通过哈希函数得到的“索引分布越均匀越好”
  • 对于一些特殊领域,有特殊领域的哈希函数设计方式,甚至有专门的论文

这里介绍的主要是一般的哈希函数的设计原则:

  • 大整数
    模一个素数 -- 数论中的理论,模一个质数会大大减少hash冲突,一般创建的hashTable储存链表或者红黑树的数组的长度就是这个质数的大小(根据key获得hashCode之后按照这个质数取模),而这个模的大小也直接影响了HashTable的性能,如果太小,哈希冲突就会更多,如果太大就太浪费空间。
大整数的哈希函数设计.png
  • 浮点数
    利用底层储存原理转换为整型(只占32位或者64位空间)
浮点型哈希函数设计.png
  • 字符串
    转成整型处理
字符串哈希函数设计.png
字符串的哈希函数设计表达式.png
  • 复合类型
复合类型.png

注意:转成整型的处理,并不是唯一的方法!

哈希函数设计的原则:

  • 一致性:如果a==b,则hash(a) == hash(b)
  • 高效性:计算高效简便
  • 均匀性:哈希值均匀分布

Java中hashCode方法

Object中定义的hashCode是根据创建的对象的地址值映射成一个整型进行设计的;
由于Object类中定义了hashCode方法,所以java中的任何对象都可以进行hash运算。

覆盖Object中的hashCode方法
覆盖Object中的equals方法(判断两个类是否相同)

@Override
public int hashCode() {
    // 生成hashCode的逻辑
    
    return 0;
}
    
@Override
public boolean equals(Object o) {
    if(this == o)
        return true;

    if(o == null)
        return false;

    if(getClass() != o.getClass())
        return false;

    return "自定义的业务逻辑".equals("");
}

链地址法 Separate Chaining

哈希冲突的处理:链地址法

补充:去符号的方法;位与运算number & 0x7fffffff
注意:这种方式并不等于Math.abs(number)

链地址法1.png

Java中TreeMap是一颗红黑树
Java中的HashMap与HashSet

Java中解决hash冲突的方法.png

哈希表链地址法 时间复杂度分析

假设有M个地址,如果放入哈希表的元素为N,由于hash函数的设计就是为了满足hash值非常平均,如果每个地址是链表,那么平均复杂度O(N/M);,如果每个地址是平衡树,那么平均复杂度为O(lon(N/M)),但是,在信息安全领域,有一种,哈希碰撞攻击 (了解了哈希值的计算方法之后,精心的设计一套数据,从而产生极端的hash冲突。)

哈希表时间复杂度分析1.png

如何将时间复杂度改为O(1)呢?

  1. 数组应该是动态改变的

  2. 平均每个地址承载的元素多个一定的程度,就进行扩容N/M >= upperTol

  3. 平均每个地址承载的元素烧过一定的程度,就进行缩容N/M < lowerTOl

哈希表的动态空间处理.png

注意:在计算的时候最好转换成乘法,因为是整型的计算

哈希表更复杂的动态空间处理方法

哈希表的均摊复杂度分析

哈希表的均摊复杂度分析.png

更复杂的动态空间处理方法

之前的扩容方式为 M -> 2 * M,很显然这样扩容之后数组的长度不是一个素数,这会让hash分布不均匀.

解决方案

更复杂的动态空间处理方法.png

补充:1610612741是素数中逼近int类型数组可以承载的极限值!相邻的两个数也基本保持的两倍的关系!

详见代码

import java.util.TreeMap;

public class HashTable<K extends Comparable<K>, V> {

    // 保存扩容与缩容推荐的素数值
    private final int[] capacity
            = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
            49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
            12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};

    private static final int upperTol = 10;
    private static final int lowerTol = 2;
    private int capacityIndex = 0;

    private TreeMap<K, V>[] hashtable;
    private int size;
    private int M; // 质数,数组的长度

    public HashTable() {
        this.M = capacity[capacityIndex];
        this.size = 0;
        this.hashtable = new TreeMap[M];
        for (int i = 0; i < this.M; i++) {
            // 每一个元素对应一个TreeMap,不可能为null
            hashtable[i] = new TreeMap<>();
        }
    }

    private int hash(K key) {
        // 获取Java提供的计算hashCode的值
        // 并取出负值,然后取模得到数组对应的索引
        return (key.hashCode() & 0x7fffffff) % M;
    }

    public int getSize() {
        return this.size;
    }

    public void add(K key, V value) {
        TreeMap<K, V> map = hashtable[hash(key)];
        if(map.containsKey(key))
            map.put(key, value);
        else {
            map.put(key, value);
            size ++;

            if(size >= upperTol * M && capacityIndex + 1 < capacity.length) {
                capacityIndex ++;
                resize(capacity[capacityIndex]);
            }
        }
        
    }

    public V remove(K key) {
        V ret = null;
        TreeMap<K, V> map = hashtable[hash(key)];
        if(map.containsKey(key)) {
            ret =  map.remove(key);
            size --;

            if(size < lowerTol * M && capacityIndex - 1 >= 0) {
                capacityIndex --;
                resize(capacity[capacityIndex]);
            }

        }

        return ret;
    }

    public void set(K key, V value) {
        TreeMap<K, V> map = hashtable[hash(key)];
        if(!map.containsKey(key))
            throw new IllegalArgumentException(key + " doesn't exit!");

        map.put(key, value);
    }

    public boolean contains(K key) {
        return hashtable[hash(key)].containsKey(key);
    }

    public V get(K key) {
        return hashtable[hash(key)].get(key);
    }

    private void resize(int newM) {
        TreeMap<K, V>[] newHashTable = new TreeMap[newM];
        for (int i = 0; i < newM; i++) {
            newHashTable[i] = new TreeMap<>();
        }

        int oldM = this.M;
        this.M = newM;
        for (int i = 0; i < oldM; i++) {
            TreeMap<K, V> map = hashtable[i];
            for(K key : map.keySet()) {
                newHashTable[hash(key)].put(key, map.get(key));
            }
        }

        this.hashtable = newHashTable;
    }
}

哈希表的均摊复杂度为O(1),非常有性能优势,但是,牺牲了什么呢?答案就是失去了顺序性

基于这些特性,集合映射可分为

  • 有序集合有序映射 --- >平衡树实现
  • 无序集合无序映射 ---> 哈希表

!!我们的哈希表的bug:-!!

我们hash表内部是使用TreeMap,必须要求Comparable,但是hash表的原则是无序的,不需要可比较!

我们设计的hash有bug.png

更多哈希冲突的处理办法

  1. 开放地址法
    所有地址对所有元素开放
    哈希冲突的处理方式

    • 线性探测:遇到哈希冲突就 + 1
    • 平方探测:遇到哈希冲突就 +1 +4 +9 +16等等
    • 二次哈希法:遇到哈希冲突, + hash2(key)
  2. 再哈希法

  3. Coalesced Hashing
    比较复杂,综合了Seperate Chaining 和 Open Addressing的特点

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 一.概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可...
    lfp901020阅读 2,971评论 0 2
  • 转载自:https://halfrost.com/go_map_chapter_one/ https://half...
    HuJay阅读 6,132评论 1 5
  • 开发工具 1.Eclipse IDE:采用Maven项目管理,模块化。 2.代码生成:通过界面方式简单配置,自动生...
    swiftie10阅读 143评论 0 1
  • 学习压力开始增大,肖娅剪了短发,戴上了白羽送的向日葵发卡。他也不太在意那些与学习无关的事,她不喜欢八卦,至于庭熙到...
    Siawane瓦妮阅读 199评论 0 3
  • 人生很短暂,青春亦是如此。你青春还剩多少? 随着时间的逝去,你是否已经学会了爱惜自己,爱惜自己的生命,珍惜自己的时...
    老原qwq阅读 177评论 0 0