冲突并非一件严重的事情,由于咱们能够用一些方式去解决它html
解决冲突的方式有三种: 拉链法,线性探测法和再哈希法java
拉链法是基于链表实现的查找表去实现的,关于链表查找表能够看下我以前写的这篇文章:算法
编写哈希函数数组
/** * @description: 根据输入的键获取对应的哈希值 */ private int hash (Key key) { return (key.hashCode() & 0x7fffffff) % M; }
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 } }