【算法】哈希表的诞生(Java)
为什么要使用哈希表
哈希表的取舍
使用哈希表的前提
哈希函数的构造
1.直接定址法


2.数字分析法


3. 平方取中法


4.折叠法


5.除留余数法


哈希地址的冲突


解决冲突的方法
冲突并不是一件严重的事情,因为我们可以用一些方式去解决它
解决冲突的方式有三种: 拉链法,线性探测法和再哈希法
拉链法
拉链法是基于链表实现的查找表去实现的,关于链表查找表可以看下我之前写的这篇文章:




编写哈希函数
- /**
- * @description: 根据输入的键获取对应的哈希值
- */
- private int hash (Key key) {
- return (key.hashCode() & 0x7fffffff) % M;
- }
- SeparateChainingHashST.java: 拉链法实现的哈希表
- SequentialSearchST.java: 链表查找表
- Test.java: 测试代码
- public class SeparateChainingHashST<Key,Value> {
- private int M; // 数组的大小
- private SequentialSearchST<Key, Value> [] st; // 链表查找表对象组成的数组
- public SeparateChainingHashST (int M) {
- st= new SequentialSearchST [M];
- this.M = M;
- // 初始化数组st中的链表对象
- for (int i=0;i<st.length;i++) {
- st[i] = new SequentialSearchST();
- }
- }
- /**
- * @description: 根据输入的键获取对应的哈希值
- */
- private int hash (Key key) {
- return (key.hashCode() & 0x7fffffff) % M;
- }
- /**
- * @description: 根据给定键获取值
- */
- public Value get (Key key) {
- return st[hash(key)].get(key);
- }
- /**
- * @description: 向表中插入键值对
- */
- public void put (Key key, Value val) {
- st[hash(key)].put(key, val);
- }
- /**
- * @description: 根据给定键删除键值对
- */
- public void delete (Key key) {
- st[hash(key)].delete(key);
- }
- }
- public class SequentialSearchST<Key, Value> {
- Node first; // 头节点
- int N = 0; // 链表长度
- private class Node {
- Key key;
- Value value;
- Node next; // 指向下一个节点
- public Node (Key key,Value value,Node next) {
- this.key = key;
- this.value = value;
- this.next = next;
- }
- }
- public int size () {
- return N;
- }
- public void put (Key key, Value value) {
- for(Node n=first;n!=null;n=n.next) { // 遍历链表节点
- if(n.key == key) { // 查找到给定的key,则更新相应的value
- n.value = value;
- return;
- }
- }
- // 遍历完所有的节点都没有查找到给定key
- // 1. 创建新节点,并和原first节点建立“next”的联系,从而加入链表
- // 2. 将first变量修改为新加入的节点
- first = new Node(key,value,first);
- N++; // 增加字典(链表)的长度
- }
- public Value get (Key key) {
- for(Node n=first;n!=null;n=n.next) {
- if(n.key.equals(key)) return n.value;
- }
- return null;
- }
- public void delete (Key key) {
- if (N == 1) {
- first = null;
- return ;
- }
- for(Node n =first;n!=null;n=n.next) {
- if(n.next.key.equals(key)) {
- n.next = n.next.next;
- N--;
- return ;
- }
- }
- }
- }
- public class Test {
- public static void main (String args[]) {
- SeparateChainingHashST<String, Integer> hashST = new SeparateChainingHashST<>(16);
- hashST.put("A",1); // 插入键值对 A - 1
- hashST.put("B",2); // 插入键值对 B - 2
- hashST.delete("B"); // 删除键值对 B - 2
- System.out.println(hashST.get("A")); // 输出 1
- System.out.println(hashST.get("B")); // 输出 null
- }
- }
线性探测法
- public class LinearProbingHashST<Key, Value> {
- private int M; // 数组的大小
- private int N; // 键值对对数
- private Key [] keys;
- private Value [] vals;
- public LinearProbingHashST (int M) {
- this.M = M;
- keys = (Key []) new Object[M];
- vals = (Value[]) new Object[M];
- }
- /**
- * @description: 获取哈希值
- */
- private int hash (Key key) {
- return (key.hashCode() & 0x7fffffff) % M;
- }
- /**
- * @description: 插入操作
- */
- public void put (Key key, Value val) // 具体代码下文给出
- /**
- * @description: 根据给定键获取值
- */
- public Value get (Key key) // 具体代码下文给出
- /**
- * @description: 删除操作
- */
- public void delete (Key key) // 具体代码下文给出
- }
- 插入操作是小偷藏进箱子的过程;
- 查找操作是警察寻找某个小偷的过程;
- 删除操作是小偷被警察抓获,同时离开箱子的过程
插入操作
- 该位置键为空,则插入键值对
- 该位置键不为空,但已有键和给定键相等,则更新对应的值
- 该位置键和给定键不同,则继续检查下一个键




- /**
- * @description: 调整数组大小
- */
- private void resize (int max) {
- Key [] temp = (Key [])new Object[max];
- for (int i =0;i<keys.length;i++) {
- temp[i] = keys[i];
- }
- keys = temp;
- }
- /**
- * @description: 插入操作
- */
- public void put (Key key, Value val) {
- // 当键值对数量已经超过数组一半时,将数组长度扩大一倍
- if(N>(M/2)) resize(2*M);
- // 计算哈希值,求出键的位置
- int i = hash(key);
- // 判断该位置键是否为空
- while(keys[i]!=null) {
- if(key.equals(keys[i])) {
- // 该位置的键和给定key相同,则更新对应的值
- vals[i] = val;
- return;
- } else {
- // 该位置的键和给定key不同,则检查下一个位置的键
- i = (i+1) % M;
- }
- }
- // 该位置键为空则插入键值对
- keys[i] = key;
- vals[i] = val;
- N++;
- return;
- }


简单思考下就能明白为什么随着键值对占数组长度的比例的增加, 哈希表的性能会下降: 因为在这个过程中,将更容易形成长的键簇(一段连续的非空键的组合)。而哈希表的查找/插入等一般都是遇到空键才能结束, 因此,长键簇越多,查找/插入的时间就越长,哈希表的性能也就越差

查找操作




- /**
- * @description: 根据给定键获取值
- */
- public Value get (Key key) {
- for (int i=hash(key);keys[i]!=null;i=(i+1)%M) {
- if (key.equals(keys[i])) {
- return vals[i];
- }
- }
- return null;
- }
删除操作






- /**
- * @description: 删除操作
- */
- public void delete (Key key) {
- // 给定键不存在,不进行删除
- if (get(key) == null) return ;
- // 计算哈希值, 求得键的位置
- int i = hash(key);
- // 获取给定键的下标
- while (!key.equals(keys[i])) {
- i = (i+1) % M;
- }
- // 删除键值对
- keys[i] = null;
- vals[i] = null;
- // 对被删除键后面键簇的所有键都进行删除并重新插入
- i = (i+1)%M;
- while (keys[i]!=null) {
- Key redoKey = keys[i];
- Value redoVal = vals[i];
- keys[i] = null;
- vals[i] = null;
- put(redoKey,redoVal);
- i = (1+1) % M;
- }
- N--;
- }
- public class LinearProbingHashST<Key, Value> {
- private int M; // 数组的大小
- private int N; // 键值对对数
- private Key [] keys;
- private Value [] vals;
- public LinearProbingHashST (int M) {
- this.M = M;
- keys = (Key []) new Object[M];
- vals = (Value[]) new Object[M];
- }
- /**
- * @description: 获取哈希值
- */
- private int hash (Key key) {
- return (key.hashCode() & 0x7fffffff) % M;
- }
- /**
- * @description: 调整数组大小
- */
- private void resize (int max) {
- Key [] temp = (Key [])new Object[max];
- for (int i =0;i<keys.length;i++) {
- temp[i] = keys[i];
- }
- keys = temp;
- }
- /**
- * @description: 插入操作
- */
- public void put (Key key, Value val) {
- // 当键值对数量已经超过数组一半时,将数组长度扩大一倍
- if(N>(M/2)) resize(2*M);
- // 计算哈希值,求出键的位置
- int i = hash(key);
- // 判断该位置键是否为空
- while(keys[i]!=null) {
- if(key.equals(keys[i])) {
- // 该位置的键和给定key相同,则更新对应的值
- vals[i] = val;
- return;
- } else {
- // 该位置的键和给定key不同,则检查下一个位置的键
- i = (i+1) % M;
- }
- }
- // 该位置键为空则插入键值对
- keys[i] = key;
- vals[i] = val;
- N++;
- return;
- }
- /**
- * @description: 根据给定键获取值
- */
- public Value get (Key key) {
- for (int i=hash(key);keys[i]!=null;i=(i+1)%M) {
- if (key.equals(keys[i])) {
- return vals[i];
- }
- }
- return null;
- }
- /**
- * @description: 删除操作
- */
- public void delete (Key key) {
- // 给定键不存在,不进行删除
- if (get(key) == null) return ;
- // 计算哈希值, 求得键的位置
- int i = hash(key);
- // 获取给定键的下标
- while (!key.equals(keys[i])) {
- i = (i+1) % M;
- }
- // 删除键值对
- keys[i] = null;
- vals[i] = null;
- // 对被删除键后面键簇的键的位置进行删除并重新插入
- i = (i+1)%M;
- while (keys[i]!=null) {
- Key redoKey = keys[i];
- Value redoVal = vals[i];
- keys[i] = null;
- vals[i] = null;
- put(redoKey,redoVal);
- i = (1+1) % M;
- }
- N--;
- }
- }
- public class Test {
- public static void main (String args[]) {
- LinearProbingHashST<String, Integer> lst = new LinearProbingHashST<>(10);
- lst.put("A",1);
- lst.put("B",2);
- lst.delete("A");
- System.out.println(lst.get("A")); // 输出null
- System.out.println(lst.get("B")); // 输出 2
- }
- }
再哈希法



【算法】哈希表的诞生(Java)的更多相关文章
- 数据结构和算法(Golang实现)(26)查找算法-哈希表
哈希表:散列查找 一.线性查找 我们要通过一个键key来查找相应的值value.有一种最简单的方式,就是将键值对存放在链表里,然后遍历链表来查找是否存在key,存在则更新键对应的值,不存在则将键值对链 ...
- Java数据结构和算法 - 哈希表
Q: 如何快速地存取员工的信息? A: 假设现在要写一个程序,存取一个公司的员工记录,这个小公司大约有1000个员工,每个员工记录需要1024个字节的存储空间,因此整个数据库的大小约为1MB.一般的计 ...
- 哈希表hashTable的Java设计
1:哈希表的概念 2:设计原理 3:哈希表的Java设计
- python数据结构与算法——哈希表
哈希表 学习笔记 参考翻译自:<复杂性思考> 及对应的online版本:http://greenteapress.com/complexity/html/thinkcomplexity00 ...
- 哈希表(散列表)—Hash表解决地址冲突 C语言实现
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.具体的介绍网上有很详 ...
- 哈希表的C实现(一)
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.具体的介绍网上有很详 ...
- java数据结构和算法09(哈希表)
树的结构说得差不多了,现在我们来说说一种数据结构叫做哈希表(hash table),哈希表有是干什么用的呢?我们知道树的操作的时间复杂度通常为O(logN),那有没有更快的数据结构?当然有,那就是哈希 ...
- Java数据结构和算法(十三)——哈希表
Hash表也称散列表,也有直接译作哈希表,Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构.它基于数组,通过把关键字映射到数组的某个下标来加快查找速度,但是又和数组.链表 ...
- 【Java】 大话数据结构(13) 查找算法(4) (散列表(哈希表))
本文根据<大话数据结构>一书,实现了Java版的一个简单的散列表(哈希表). 基本概念 对关键字key,将其值存放在f(key)的存储位置上.由此,在查找时不需比较,只需计算出f(key) ...
随机推荐
- 将项目(代码)从GitHub上克隆(下载)到本地仓库
要将项目从GitHub上克隆到本地,首先你得下载并安装好git for window. 下载地址:http://www.xp510.com/xiazai/Application/other/30988 ...
- IntelliJ IDEA 2017.3下载与安装
大约在2017年暑假的时候知道了IntelliJ IDEA,但是那个时候一心认为有Eclipse就足够用了,然而今天在网上冲浪的时候发现,IntelliJ IDEA是java语言开发的集成环境,这款开 ...
- 腾讯云中ssL证书的配置安装
https://cloud.tencent.com/document/product/619/12797 配置 Nginx 和 HTTPS 完成以上准备工作,就要开始配置 Nginx 和 HTTPS ...
- PHP 常用的header头部定义汇总
http://www.jb51.net/article/68159.htm
- 紧急求助!配置SMTP插件出错,SMTP connect() failed
http://bbs.csdn.net/topics/390848222 我来挖个坟.我知道问题所在了,只要你们本地或服务器上环境中只要确保开启了php_openssl 跟 php_socket等扩展 ...
- php错误 分析
---------------------------------------------------------------------------------------------------- ...
- drawpoly()函数的用法
画多边形的函数drawpoly() 用当前绘图色.线型及线宽,画一个给定若干点所定义的多边形.第一个参数,是多边形的顶点数第二个参数,是该数组中是多边形所有顶点(x,y)坐标值,即一系列整数对
- Android查缺补漏(View篇)--事件分发机制源码分析
在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)-- ...
- Nodejs的运行原理-生态篇
前言 这里是重点:Nodejs 是由v8 engine,libuv和内置模块组成,可以将v8 engine和 libuv看成一个库,两者是以源码的方式直接编译执行node中去的. 这是一个广泛的介绍, ...
- linux nvme的那些workqueue
目前nvme三个常见的使用的workqueue ,主要有nvme_workq,nvme_rdma_wq ,nvme_fc_wq,下面一一描述一下初始化及使用的场景.分别对应于NVME over PCI ...