【算法】哈希表的诞生
为什么要使用哈希表
哈希表的取舍
使用哈希表的前提
哈希函数的构造
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 } }
再哈希法