【算法】哈希表的诞生(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) ...
 
随机推荐
- Spark_总结七_troubleshooting
			
转载标明出处 http://www.cnblogs.com/haozhengfei/p/07ef4bda071b1519f404f26503fcba44.html Spark_总结七_troubles ...
 - VN问题:error:请求的名称有效,但是找不到请求的类型的
			
把url中的jmsjms-pc换成IP地址试试看 IP地址你用的是外网地址,应该用局域网内网地址,改成内网地址再试试看 还有练习架设SVN服务器初期尽量用http协议,不要上来就用https协议,ht ...
 - yourphp目录结构
			
Yourphp企业网站管理系统是一款完全开源免费的PHP+MYSQL系统.核心采用了Thinkphp框架等众多开源软件,同时核心功能也作为开源软件发布的网站后台管理系统. 二.目录说明 /Cache ...
 - Unix/Linux命令:SED
			
在Unix/Linux系统中,sed命令采用逐行处理的方式对文件进行查找.删除.替换.添加.插入等操作. 语法:sed [OPTION]... {script-only-if-no-other-scr ...
 - Redis进阶实践之七Redis和Lua初步整合使用
			
一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运行在任何平台上,也可以嵌入到大多数语言当 ...
 - 六、Html头部和元信息
			
前面整理的都是html常用到的标签,这里整理一下html的的头部和元信息标签. 定义html都的头部要写在<head>标签里面,一般他还包含如下一些标签: 1,<script> ...
 - linkin大话设计模式--门面模式
			
linkin大话设计模式--门面模式 随着系统的不断改进和开发,他们会变得越来越复杂,系统会生成大量的类,这使得程序的流程更加难以理解.门面模式可以为这些类提供一个简易的接口,从而简化访问这些类的复杂 ...
 - 输入和输出--java序列化机制
			
对象的序列化 什么是Java对象的序列化? 对象序列化的目标是将对象保存到磁盘上,或允许在网络中直接传输对象.对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而保存或者传输.其他 ...
 - Java数据结构和算法(十四)——堆
			
在Java数据结构和算法(五)——队列中我们介绍了优先级队列,优先级队列是一种抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,优先级队列可以用有序数组来实现 ...
 - java时间格式转化(毫秒 to 00:00)
			
把秒数转换为%d:%02d:%02d 格式 private String stringForTime(int timeSec) { int totalSeconds = timeSec; int se ...