哈希表(散列表)

通过哈希函数使元素的存储位置与它 的关键码之间能够建立一一映射的关系,在查找时可以很快找到该元素。

哈希表hash table(key,value) 的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

1.哈希冲突

就是键(key)经过hash函数得到的结果作为地址去存放当前的键值对,但是却发现该地址已经有人先来了就会产生冲突。这个冲突就是hash冲突了。

2.load factoe 装载因子

已占用桶的个数/桶的总数

当装载因子大于 0.8时就应该扩容。 

由于哈希冲突的存在造成哈希表的增删查时间复杂度只能无限趋近于0(1)

3. 哈希冲突的解决

1.可以把key存放在表中的“下一个“”空位置。即从发生冲突的位置开始,依次向后探测,直到找到空位置为止(线性探测)。

2.实现链式哈希表,从根本上说是由一组链表构成。每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(该过程称为哈希键),函数通过散列的方式告知元素属于哪个“桶”,然后在相应的链表头插入元素。查找或删除元素时,用同样的方式先找到元素的“桶”,然后遍历相应的链表,直到发现我们想要的元素。因为每个“桶”都是一个链表,所以链式哈希表并不限制包含元素的个数。然而,如果表变得太大,它的性能将会降低。

3.优点:增删改查O(1)

   缺点:1.占用内存比较大
              2.元素没有任何顺序

4.源代码:

线性探测哈希表

class LinerHashMap<T extends Comparable<T>>{
// 散列表数组
private Entry<T>[] hashTable;
// 被占用的桶的个数
private int usedBucketNum;
// 哈希表的装载因子
private double loadFactor;
// 定义素数表
private static int[] primTable;
// 记录当前使用的素数的下标
private int primIndex; // 类的静态初始化块
static{
primTable = new int[]{3, 7, 23, 47, 97, 127};
} /**
* 构造函数,初始化
*/
public LinerHashMap(){
this.primIndex = 0;
this.hashTable = new Entry[primTable[this.primIndex]];
this.usedBucketNum = 0;
this.loadFactor = 0.75;
} /**
* 增加元素
* @param key
*/
public void put(T key){
// 计算哈希表是否需要扩容
double ret = this.usedBucketNum*1.0 / this.hashTable.length;
if(ret > this.loadFactor){
resize(); // 哈希表的扩容
} // 先计算key应该放的桶的下标
int index = key.hashCode() % this.hashTable.length;
int idx = index;
do{
// 表示是从未使用过的桶
if(this.hashTable[idx] == null){
this.hashTable[idx] = new Entry<>(key, State.USING);
this.usedBucketNum++;
return;
} // 表示使用过的桶
if(this.hashTable[idx].getState() == State.USED){
this.hashTable[idx].setData(key);
this.hashTable[idx].setState(State.USING);
this.usedBucketNum++;
return;
} else {
// 正在使用中的桶,不插入重复元素
if(this.hashTable[idx].getData().compareTo(key) == 0){
return;
}
}
idx = (idx+1)%this.hashTable.length;
} while(idx != index);
} /**
* 哈希表的扩容函数
*/
private void resize() {
Entry<T>[] oldHashTable = this.hashTable;
this.hashTable = new Entry[primTable[++this.primIndex]];
this.usedBucketNum = 0; for (int i = 0; i < oldHashTable.length; i++) {
if(oldHashTable[i] != null
&& oldHashTable[i].getState() == State.USING){
this.put(oldHashTable[i].getData());
this.usedBucketNum++;
}
}
} /**
* 删除元素
* @param key
*/
public void remove(T key){
// 先计算key应该放的桶的下标
int index = key.hashCode() % this.hashTable.length; // 从当前位置开始找元素
int idx = index;
do{
// 如果遍历桶的过程中,发现了从未使用过的桶,直接返回
if(this.hashTable[idx] == null){
return;
}
if(this.hashTable[idx].getState() == State.USING
&& this.hashTable[idx].getData().compareTo(key) == 0){
this.hashTable[idx].setData(null);
this.hashTable[idx].setState(State.USED);
this.usedBucketNum--;
return;
}
idx = (idx+1)%this.hashTable.length;
} while(idx != index);
} /**
* 查询元素 返回key的值,找不到返回null
* HashMap
* @param key
* @return
*/
public T get(T key){
// 先计算key应该放的桶的下标
int index = key.hashCode() % this.hashTable.length; // 从当前位置开始找元素
int idx = index;
do{
// 如果遍历桶的过程中,发现了从未使用过的桶,直接返回
if(this.hashTable[idx] == null){
return null;
}
if(this.hashTable[idx].getState() == State.USING
&& this.hashTable[idx].getData().compareTo(key) == 0){
return key;
}
idx = (idx+1)%this.hashTable.length;
} while(idx != index); return null;
} /**
* 定义桶的状态值
*/
static enum State{
UNUSE,// 桶从未使用过
USED,// 桶被用过了
USING// 桶正在使用中
} /**
* 定义桶的元素类型
* @param <T>
*/
static class Entry<T extends Comparable<T>>{
T data;
State state; public Entry(T data, State state) {
this.data = data;
this.state = state;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public State getState() {
return state;
} public void setState(State state) {
this.state = state;
}
}
}

链式探测哈希表

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

    // 哈希桶
private Entry<K,V>[] table;
// 装载因子 0.75
private double loadFactor;
// 记录已经占用的桶的数量
private int usedBucketSize; /**
* 哈希表初始化
*/
public LinkHashTable(){
this.table = new Entry[3];
this.loadFactor = 0.75;
this.usedBucketSize = 0;
} /**
* 给哈希表增加元素
* @param key
* @param value
*/
public void put(K key, V value){
if(this.usedBucketSize*1.0/this.table.length>this.loadFactor){
this.expand();
}
int idx = key.hashCode() % this.table.length;
if(this.table[idx]==null){
this.table[idx]=new Entry<>(key,value,null);
this.usedBucketSize++;
return;
}
Entry<K,V>entry=this.table[idx];
//判断是否有这个key,如果有直接替换
while (entry!=null){
if(entry.key.compareTo(key)==0){
entry.value=value;
return;
}
entry=entry.next;
}
this.table[idx]=new Entry<>(key,value,this.table[idx]);
} /**
* 在哈希表中查询key是否存在,如果key存在,返回它对应的value值,
* 否则返回null
* @param key
* @return
*/
public V get(K key){
int idx = key.hashCode() % this.table.length;
if(this.table[idx]==null){
return null;
}
Entry<K,V>entry=this.table[idx];
while (entry!=null){
if(entry.key.compareTo(key)==0){
return entry.value;
}
entry=entry.next;
}
return null;
} /**
* 删除哈希表中key值为参数指定的节点
* @param key
*/
public void remove(K key){
int index = key.hashCode() % this.table.length;
if(this.table[index]==null){
return;
}else if(this.table[index].key.compareTo(key)==0){
this.table[index]=this.table[index].next;
return;
}
Entry<K,V>entry=this.table[index];
Entry<K,V>entry1=entry.next;
if(entry1!=null){
if(entry1.key.compareTo(key)==0){
entry.next=entry1.next;
}
entry=entry.next;
entry1=entry1.next;
}
if(this.table[index]==null){
this.usedBucketSize--;
}
} /**
* 哈希表的扩容函数
*/
private void expand() {
Entry<K, V>[] oldTable = this.table;
this.table = new Entry[oldTable.length * 2 + 1];
this.usedBucketSize = 0;
for (int i = 0; i < oldTable.length; i++) {
if (oldTable[i] != null) {
this.put(oldTable[i].key, oldTable[i].value);
}
}
}
/**
* 链式哈希表中节点的类型
* @param <K,V>
*/
static class Entry<K extends Comparable<K>,V> {
K key; // student id
V value; // student
Entry<K, V> next; public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
}

最新文章

  1. 数据泵如何生成导出文件的DDL脚本
  2. linux创建用户、设置密码、修改用户、删除用户
  3. [轉]Android的内存泄漏和调试
  4. 【log4net】配置文件
  5. Android 不通过parent实现样式继承
  6. openwrt+ndp+ndppd+radvd+dhcpv6,ipv6穿透配置指南
  7. Java数据库连接--JDBC调用存储过程,事务管理和高级应用
  8. PVM的安装和编译PVM程序
  9. Swing-JCheckBox用法-入门
  10. JS设计模式(二) 惰性模式
  11. Dynamics CRM2016 Web Api之根据时间查询数据
  12. 开源项目——小Q聊天机器人V1.1
  13. 转://11g之后,通过v$wait_chains视图诊断数据库hang和Contention
  14. layer结合art实现弹出功能
  15. About Gnu Linker1
  16. Javascript \x 反斜杠x 16进制 编解码
  17. 【Java】-NO.16.EBook.4.Java.1.004-【疯狂Java讲义第3版 李刚】- 内部类
  18. spring-mvc.xml与spring-mybatis.xml配置文件中命名空间问题
  19. JSP内置对象——response对象
  20. php分享二十三:字符编码

热门文章

  1. MFS分布式文件系统【1】概述
  2. beforeEach的深入研究,及beforeEach和beforeRouteEnter区别?
  3. .net core 根据环境变量区分正式、测试 配置文件
  4. Java中在磁盘上复制文件
  5. box-shadow单侧投影,双侧投影,不规则图案投影
  6. Linux下的Ngnix服务器部署静态页
  7. CSIC_716_20191205【TCP-解决粘包问题、UDP模板】
  8. Shell脚本 全局变量、局部变量
  9. 每天进步一点点-Tesseract 文字识别
  10. 区别 |python |[-1]、[:-1]、[::-1]、[2::-1]的使用