知识补充
hashMap
是Map
的具体实现类。Map
是一个接口类,该类没有继承自Collection,该类中存储的是键值对,并且键唯一,不能重复。
键值对:就是一个唯一的key对应一个值。举个例子:就像学生的学号,每个学生的学号对应一个同学的姓名,学号是唯一的,学生的姓名可以重复。也就是说,键值对中键唯一,一个键对应一个值,但是也可以有多个键对应相同的值。就像学校有多个叫王五的同学,他们的学号唯一,但是对应的姓名都是王五。
注意:
- Map是一个接口,不能实例化对象。如果要实例化对象只能实例化其实现类
- Map中的键是唯一的,value是可以重复的
- 在Map中插入键值对,键不能为空,值可以为空
- Map中键值对的key不能直接修改,value可以直接修改,如果要修改key,只能先将key删除掉再插入
hashMap概念
在顺序结构和二叉搜索树中,元素与其位置没有一一对应的关系,因此在查找一个元素时,必须要经过比较遍历。顺序查找的时间复杂度为O(n),二叉搜索树和二分查找为O(logn),搜索的效率取决于元素比较的次数。
理想的搜索方法就是:不经过任何比较,依次从表中找到想要的元素。如果能构造一种存储结构,通过某种函数使元素的存储位置和元素之间能够建立起对应的关系,那么在查找时,就可以很快找到该元素。
该方法即为哈希(散列)方法,哈希方法中使用的转换函数为称为哈希函数,构造出来的结构称为哈希表。
存储方式:
-
插入元素
根据插入元素的关键字,使用哈希函数来计算出该元素的存储位置,并按此位置存放
-
查找元素
对元素的关键字进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置比较,若关键字相等,则找到该元素
例如集合:{1,8,4,9,2,3,5}
哈希函数设置为:hash(key) = key % capacity
,capacity为存储元素底层空间的大小
假设哈希表的默认存储容量为10,那么我们按照哈希函数hash(key) = key % capacity
来得出每一个关键字存储的位置,并将关键字放进去。同样取数据时,我们知道key,就可以用哈希函数找到存储key的位置,然后找到该元素。然而,按照如上方式继续插入19时,就会发现9号地址已经有关键字。此时该怎么做?扩容?然后放到下标为19的地址吗?
哈希冲突
我们发现,对于两个不同的关键字key,使用哈希函数计算之后得到了相同的地址,这种现象称为哈希冲突。这种冲突如何避免呢?单纯的扩容肯定是不行的,假如集合元素为{9,19,29,39,49,59}难道为了存储这几个数据去开60的空间吗?
冲突避免–哈希函数设计
我们需要明确的是,哈希表底层数组的容量往往是小于我们实际要存储的关键字的数量的,意思就是哈希冲突的发生是必然的,我们要想办法尽量降低冲突率
引起哈希冲突原因:哈希函数设计不合理
哈希函数的设计原则:
- 哈希函数的定义域必须要包括全部的关键字,如果哈希表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出的地址能均匀分布在整个存储空间
- 哈希函数应该比较简单
常见哈希函数
-
直接定制法
取关键字的某个线性函数为散列地址:
hash(key) = A*key + B
优点:简单、均匀 缺点:需要事先知道关键字的分布情况
例如:字符串中的第一个唯一字符
使用哈希表的思想,我们知道字母有26个,创建大小为26的字符数组,通过哈希函数
char - 'a'
得到key的地址(下标),遍历过程中,每出现一次相同的key就让chars[char - 'a']
地址的值加一。然后我们得到了第i
个字母在字符串中出现的次数即chars[i]
,最后遍历字符串,找chars[char - 'a']
位置的值是否为1
,遇到的第一个为1
的即返回结果。public int firstUniqChar(String s) { char[] chars = new char[26]; for(char c : s.toCharArray()<