哈希表

引入

字符串中的第一个唯一字符,这一道题是leetcode上的一道题,要想知道字符串中的第一个唯一字符,就得知道字符串中所有字符出现的次数,这时,我们就想到将字符串的每一个第一次字符存放到数组中,如果第二次出现这个字符,那么让freq[s.charAt(i) - 'a'] ++,第二次for循环,判断第一个字符出现1,如果为1那么返回其索引。

class Solution{
    public int firstUniqChar(String s){
        //创建一个可以存放26个元素的数组空间
        int[] freq = new int[26];

        for (int i = 0; i < s.length();i ++){
            freq[s.charAt(i) - 'a'] ++;
        }
        for (int i = 0; i < s.length(); i ++){
            if (freq[s.charAt(i) - 'a'] == 1){
                return i;
            }
        }
        return -1;
    }
}

含义

哈希表(Hash table),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做哈希表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
对于上题:关键字的值为26个字符,代入函数s.charAt(i) - 'a'(哈希函数),得到了对应字符的ASCII码(唯一),将其存放在数组。这个数组就是哈希表。
通俗来讲就是:每一个字符都和一个索引相对应,我们很难保证每一个“键”通过哈希函数的转换对应不同的索引,这时候就产生了哈希冲突
哈希表充分体现了算法领域的经典思想:空间换时间

设计

  1. 如何设计哈希函数
  2. 如何处理哈希冲突

“键“通过哈希函数得到的“索引”分布越均匀越好,在进行转化的时候,小范围的正整数可以直接使用,小范围的负整数进行偏移后使用(-100 ~100 偏移后 0 ~ 200)。

原则:

  1. 一致性:若a==b,则hash(a) == hash(b)
  2. 高效性:计算高效便捷
  3. 均匀性:哈希值均匀分布

大整数:

模一个素数
为何选取素数

浮点型

在计算机中都是32位或者64位的二进制表示,只不过计算机解析成了浮点数。

字符串

转成整型处理
在这里插入图片描述
为了简化运算以及避免整型的溢出所以进行一些形式上的改变
在这里插入图片描述
代码的实现上就是遍历字符串,拆分成字符,让hash值累加

int hash = 0;
for (int i =0; i< str.length(); i++){
     hash = (hash * B + str.charAt(i)) % M;
}

复合类型

对于复合类型我们可以将其按照字符串类型处理
在这里插入图片描述

Java中的hashCode

public class Student {
    int grade;
    int cls;
    String firstName;
    String lastName;

    public Student(int grade, int cls, String firstName, String lastName) {
        this.grade = grade;
        this.cls = cls;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public int hashCode() {
        //由于不知道取多少的模,所以M不写
        int B = 31;
        int hash = 0;
        hash = hash * B + grade;
        hash = hash * B + cls;
        //由于firstName、lastName是String类型直接调用hashCode函数就可以了
        hash = hash * B + firstName.toLowerCase().hashCode();
        hash = hash * B + lastName.toLowerCase().hashCode();
        return hash;
    }
}

在上面的代码中,我们重写了hashCode方法,对于不同Student的对象,输入的值相同的时候,所产生的hash值是相同的,而当我们调用Object中所带的hashCode方法的时候,是根据每一个创建Object对象的地址,把它转化为整型,然后产生对应的哈希值,所产生的值是不同的。
通过,以上的讲述,所以我们在编写程序的时候根据逻辑、需求来判断是否覆盖Object的hashCode方法。
当我们使用hashSet或者hashMap的时候,自己写的hashCode用于帮助我们计算那个hash函数的值,但是在产生哈希冲突的时候,依旧需要比较不同的对象他们之间是否是相等的,此时只覆盖hashCode是不行的,还要覆盖equals方法。

对于hashCode和equals方法有如下解释:

  1. 如果两个对象equals,那么它们的hashCode必然相等,
  2. 但是hashCode相等,equals不一定相等。

那么,当往里面插数据时,是以hashCode作为key插入的,一般hashCode%8得到所在的索引,如果所在索引处有元素了,则使用一个链表,把多的元素不断链接到该位置,这边也就是大概提一下HashMap原理。所以hashCode的作用就是找到索引的位置,然后再用equals去比较元素是不是相等,形象一点就是先找到桶(bucket),然后再在里面找东西。

哈希冲突的处理

链地址法(Separate Chaining)

哈希表的本质就是一个数组,当我们传入一个值的时候,对其进行hashCode然后将它对一个素数取模,在有的地方是这样写的(hashCode(k1) & 0* 7fffffff) % M, 他不是直接取模,而是进行一个按位与操作后,在进行和素数取模。

按位与运算符“&”是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位都为1时,结果位才为1。

在16进制的表示法中,每一位表示4个bit,那么f的二进制就是1111,那么7个f表示的就是28个1,7的二进制表示是111,那么28 + 3 = 31,此时第一位为0,所以16进制的0* 7fffffff转换为二进制就是01111111111111111111111111111111,最高位是符号位,0表示正数,1表示负数,假若hashCode的值为正数,那么按位与运算后,是0,还是正数,若为负数,1和0按位与后的值为0,还是正数,这样的话就相当于去掉符号。
当有重复的哈希值存入的时候,这时候就可以将新的值放在后面,生成一个一个链表,这时候,链表可以使用树这一结构替换掉,所以可以说HashMap就是一个TreeMap数组,HashSet就是一个HashSet数组。在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在JavaScript中,哈希表可以通过对象(Object)来实现。对象的属性名就是哈希表中的键,属性值就是哈希表中的值。以下是一个简单的哈希表的实现: 1. 定义一个HashTable类,包含以下方法: ```javascript class HashTable { constructor() { this.table = {}; } // 向哈希表中添加键值对 put(key, value) { this.table[key] = value; } // 从哈希表中获取指定键的值 get(key) { return this.table[key]; } // 从哈希表中移除指定键的值 remove(key) { delete this.table[key]; } // 判断哈希表中是否包含指定键 contains(key) { return this.table.hasOwnProperty(key); } // 获取哈希表中所有的键 getKeys() { return Object.keys(this.table); } // 获取哈希表中所有的值 getValues() { return Object.values(this.table); } // 获取哈希表中键值对的数量 size() { return Object.keys(this.table).length; } // 清空哈希表 clear() { this.table = {}; } } ``` 2. 创建一个HashTable对象,并向其中添加键值对: ```javascript const hashTable = new HashTable(); hashTable.put('name', 'Tom'); hashTable.put('age', 18); hashTable.put('gender', 'male'); ``` 3. 从哈希表中获取指定键的值: ```javascript const name = hashTable.get('name'); // 'Tom' ``` 4. 从哈希表中移除指定键的值: ```javascript hashTable.remove('gender'); ``` 5. 判断哈希表中是否包含指定键: ```javascript const hasAge = hashTable.contains('age'); // true const hasGender = hashTable.contains('gender'); // false ``` 6. 获取哈希表中所有的键: ```javascript const keys = hashTable.getKeys(); // ['name', 'age'] ``` 7. 获取哈希表中所有的值: ```javascript const values = hashTable.getValues(); // ['Tom', 18] ``` 8. 获取哈希表中键值对的数量: ```javascript const size = hashTable.size(); // 2 ``` 9. 清空哈希表: ```javascript hashTable.clear(); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值