哈希表(散列表)

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

哈希表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;
}
}
}

java哈希表(线性探测哈希表。链式哈希表)的更多相关文章

  1. 哈希表---线性探测再散列(hash)

    //哈希表---线性探测再散列 #include <iostream> #include <string> #include <stdio.h> #include ...

  2. 数据结构 链式哈希表(Hash Table)的接口定义与实现分析(完整代码)

    链式哈希表的接口定义 关于哈希表与链式哈希表的描述可以参阅:http://www.cnblogs.com/idreamo/p/7990860.html 链式哈希表的操作与属性有:初始化.销毁.插入元素 ...

  3. DS哈希查找--线性探测再散列

    题目描述 定义哈希函数为H(key) = key%11.输入表长(大于.等于11),输入关键字集合,用线性探测再散列构建哈希表,并查找给定关键字. --程序要求-- 若使用C++只能include一个 ...

  4. [置顶] ※数据结构※→☆线性表结构(queue)☆============优先队列 链式存储结构(queue priority list)(十二)

    优先队列(priority queue) 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除.在优先队列中,元素被赋予优先级.当访问元素时,具有最高优先级的元素最先删除.优先队列具有 ...

  5. 【Java】 大话数据结构(6) 栈的顺序与链式存储

    本文根据<大话数据结构>一书,实现了Java版的栈的顺序存储结构.两栈共享空间.栈的链式存储机构. 栈:限定仅在表尾进行插入和删除操作的线性表. 栈的插入(进栈)和删除(出栈)操作如下图所 ...

  6. [PTA] 数据结构与算法题目集 6-4 链式表的按序号查找 & 6-5 链式表操作集 & 6-6 带头结点的链式表操作集

    带不带头结点的差别就是,在插入和删除操作中,不带头结点的链表需要考虑两种情况:1.插入(删除)在头结点.2.在其他位置. 6.4 //L是给定单链表,函数FindKth要返回链式表的第K个元素.如果该 ...

  7. 算法与数据结构(二) 栈与队列的线性和链式表示(Swift版)

    数据结构中的栈与队列还是经常使用的,栈与队列其实就是线性表的一种应用.因为线性队列分为顺序存储和链式存储,所以栈可以分为链栈和顺序栈,队列也可分为顺序队列和链队列.本篇博客其实就是<数据结构之线 ...

  8. 仅当使用了列的列表,并且 IDENTITY_INSERT 为 ON 时,才能在表中为标识列指定显式值问题

    今天在处理数据库过程中碰到这样的问题在插入一条数据到表中 系统报这样的错误 仅当使用了列的列表,并且 IDENTITY_INSERT 为 ON 时,才能在表中为标识列指定显式值问题 表有一列是自增长的 ...

  9. java链式创建json对象

    我们主要介绍一下:java中如何通过最简单的方式实现链式创建json对象,解决创建json代码臃肿的问题. 1.假设我们要创建一个json对象格式如下: { "code": 0, ...

随机推荐

  1. web前端Vue+Django rest framework 框架 生鲜电商项目实战✍✍✍

    web前端Vue+Django rest framework 框架 生鲜电商项目实战  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频 ...

  2. web 服务中上传文件大小控制

    参考文章:https://rensanning.iteye.com/blog/2388353 项目场景: Vue + Nginx + Java + Tomcat Nginx作为反向代理服务器,访问To ...

  3. 如何清除Windows共享登录的用户名密码

    打开cmd 1.[查看已记录的登录信息] net use 2.[清除记录] 得关掉你所有打开的samba之后再 net use * /del

  4. linux 套接字

    三.命名套接字 之前的socket只是创建了一个没有名字的资源,其他进程无法访问他.所以也无法从它接受消息.只有当bind给套接字绑定了端口和名字后,其他进程才能找到它. 一般服务器是一定要bind, ...

  5. [转载]python异常如何全面捕获

    写在前面:最近写python程序,进场遇到异常的问题,因此需要捕获异常.查阅了下资料,整理如下: 常见的异常处理的方法: 假设有下面的一段程序: try:     语句1     语句2     . ...

  6. python-面向对象-01课堂笔记

    面向对象 ''''1.面向过程编程   核心是"过程"二字,过程指的是解决问题的步骤,即先干什么再干什么   基于该思想编写程序就好比在编写一条流水线,是一种机械式的思维方式​   ...

  7. Wpf 获取指定字体和大小的字符的长宽

    Wpf 获取指定字体和大小的字符的长宽 运行环境:Win10 x64, NetFrameWork 4.8, 作者:乌龙哈里,日期:2019-05-09 参考: 章节: 比如一个 Consolas 字体 ...

  8. mui框架开发aop的跨页面传值

    mui开发跨平台app,其实不乏会涉及到跨页面传值,今天给大家简单介绍一种常用也是简单的传值方法 咱在这里设置一个场景,就是两个页面进入到同一页面展示不同的元素,此时需要在这两个页面各自设置一个区别的 ...

  9. AbstractQueuedSynchronizer 详解

    package java.util.concurrent.locks; 基本介绍 AbstractQueuedSynchronizer(队列同步器)可以看作是并发包(java.util.concurr ...

  10. Go 动态类型声明

    Go 动态类型声明 package main import "fmt" func main() { var x float64 = 20.0 y := 42 fmt.Println ...