哈希表的本质:就是每一个字符转成索引储存在数组之中
哈希函数:“键”转换成“索引”
因为我们在过程中个难保证每一个键通过哈希表的转换对应不同的索引,因而会产生哈希冲突
哈希表充分体现了算法设计领域的经典思想:空间换时间
哈希表是时间和空间之间的平衡
哈希函数的设计是很重要的
键值对应越均匀越好
对于整形来说
小范围正整数直接使用就好
小范围的负整数我们需要进行偏移
大整数
通常做法:取模 取4位等同于 mod 10000(没有利用所有信息,会增加哈希冲突的概率)我们解决这个问题做好的办法就是 摸一个素数
浮点型
在计算机中都是32位或者64位的二进制表示,只不过计算机解析成了浮点数
字符串转成整型处理(不是唯一的方法)
类似于科学计数法进行处理(可以选择什么进制)最后一样进行取模操作(容易整型溢出),我们可以进行每一次乘进制加对应字母去模依次进行操作,这样就避免了这个问题,复合类型类似
原则
1.一致性:例如 a==b 则 hash(a) == hash(b)
2.高效性:例如 计算高效简便
3.均匀性:例如 哈希值均匀分布
hashCode方法(返回值是int)
对于整型来说值是多少,hashCode就是多少
Java中提供的数据结构 hashSet ,hashMap
Java中默认的hashCode是根据地址来映射整型具体的实现代码如下:
@Override
public int hashCode() {
int B = 31;
int hash = 0;
hash = hash * B + grade;
hash = hash * B + cls;
hash = hash * B + firstName.toLowerCase().hashCode();//不区分大小写
hash = hash * B + lastName.toLowerCase().hashCode();//不区分大小写
return hash;
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(o == null) {
return false;
}
Student another = (Student)o;
return this.grade == another.grade &&
this.cls == another.cls &&
this.firstName.toLowerCase().equals(another.firstName.toLowerCase())&&
this.lastName.toLowerCase().equals(another.lastName.toLowerCase());
}
哈希冲突的处理方法 链地址法
hashcode的值与16进制进行按位与然后在进行取模
HashMap就是一个TreeMap数组
HashSet就是一个TreeSet数组
实现一个哈希表具体的实现代码如下
private TreeMap<K,V>[] hashtable;
private int M;//哈希表的长度
private int size;//哈希表的容量
public HashTable(int M) {
this.M = M;
size = 0;
hashtable = new TreeMap[M];
for(int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
public HashTable() {
this(97);
}
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
public int getSize() {
return size;
}
向添加一个元素
利用键值转换成索引,然后进行添加,若果该索引出有元素,那么就直接在该元素后面添加,形成一个链表,没有的话直接添加,最后进行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++;
}
}
在哈希表中删除元素并返回
如果存在直接删除key的值即可,不存在直接返回。具体的实现代码如下
public V remove(K key) {
TreeMap<K,V> map = hashtable[hash(key)];
V ret = null;
if(map.containsKey(key)) {
ret = map.remove(key);
size--;
}
return ret;
}
修改操作
如果key不存在抛出key不存在的异常,如果存在直接进行修改即可,具体的实现代码如下:
public void set(K key,V value) {
TreeMap<K,V> map = hashtable[hash(key)];
if(!map.containsKey(key)) {
throw new IllegalArgumentException("key不存在");
}
map.put(key, value);
}
查找操作
是否存在key,直接利用contains方法即可具体的实现代码如下
public boolean contains(K key) {
return hashtable[hash(key)].containsKey(key);
}
通过key找value值
通过hash算法找到key索引,然后get方法得到value值,具体的实现代码如下:
public V get(K key) {
return hashtable[hash(key)].get(key);
}
哈希表的动态空间处理
总共有M 个地址
放入哈希表的元素为N
平均每个地址承载的元素多过一定程度,即扩容
N/M >= upperTol
平均每个地址承载的元素少过一定程度,即缩容
N/M < upperTol
具体的实现代码如下
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 = 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;
}
}
以上就是我对哈希表的理解,不喜勿喷,谢谢!