哈希表(HashMap)
·
哈希冲突与解决策略(拉链寻址、开放寻址等)
哈希冲突:当不同的键通过哈希函数映射到相同的索引位置时,就发生了哈希冲突。
解决策略:
拉链法(Chaining):每个哈希桶(数组位置)不直接存储元素,而是存储一个链表的头节点。所有哈希值相同的元素都放在这个链表中。优点:实现简单、删除操作容易、元素数量不受数组大小限制、负载因子可以大于1 。缺点:需要额外的指针空间、缓存不友好(链表节点在内存中不连续)
拉链法示意图:
// 哈希表数组 索引: 0 1 2 3 4 5 6 7 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ null null null null null null null null // 插入元素后 索引0: [ (A,10) ] → [ (B,20) ] → null (两个元素hash值都是0) 索引1: null 索引2: [ (C,30) ] → null 索引3: [ (D,40) ] → [ (E,50) ] → [ (F,60) ] → null 索引4: null ...开放寻址(Open Addressing):当发生冲突时,不是创建链表,而是通过探查方式(如线性探查、二次探查等)在数组中寻找下一个空位置存放元素。所有元素都直接存储在数组中。
线性探查:
// 当位置 i 被占用时,依次尝试 i+1, i+2, i+3, ... // 公式:hash(key) + 1, +2, +3 ... // 示例:插入 key=17,hash=1 初始数组: [null, null, null, null, null, null, null, null] 1. 插入 A(1) → 索引1 [null, A, null, null, null, null, null, null] 2. 插入 B(17) → hash=1 被占,尝试2 → 空,放入 [null, A, B, null, null, null, null, null] 3. 插入 C(33) → hash=1 被占,尝试2被占,尝试3 → 空,放入 [null, A, B, C, null, null, null, null] 4. 查找 B(17):从1开始,找到2,key匹配,返回二次探查:
// 当位置 i 被占用时,依次尝试 i+1², i+2², i+3², ... // 公式:hash(key) + 1², +2², +3² ... // 示例 hash = 3,被占: 尝试 3 + 1² = 4 尝试 3 + 2² = 7 尝试 3 + 3² = 12 ...
实现简单HashMap
实现原理:使用哈希表存储键值对。哈希函数将键映射到数组的索引位置,处理冲突时可以选择拉链法或开放寻址法。
哈希表操作:
插入:计算键的哈希值,将元素插入到对应位置。
查找:根据键的哈希值查找对应的值。
删除:根据键的哈希值删除对应的键值对。
示例:
import java.util.Objects; public class simpleHashMap<K,V> { static class Node<K, V> { final K key; // 键 V value; // 值 Node<K, V> next; // 下一个节点(处理哈希冲突) Node(K key,V value){ this.key = key; this.value = value; this.next = null; } // 所有类都继承自 Object, Object 类的默认 toString(),所以需要重写方法 @Override public String toString() { return key + "=" + value; } } private Node<K, V>[] table; // 数组(桶) private int size; // 元素个数 private static final int DEFAULT_CAPACITY = 16; // 默认容量 private static final float LOAD_FACTOR = 0.75f; // 负载因子 @SuppressWarnings("unchecked") public simpleHashMap(){ table = (Node<K,V>[]) new Node[DEFAULT_CAPACITY]; size = 0; } private int hash(K key){ //取模法哈希函数 if(key == null) return 0; int h = key.hashCode(); //hashCode()是生成一个原始的数字 return Math.abs(h)%table.length; } public V put(K key,V value){ int index = hash(key); Node<K,V> head = table[index]; Node<K,V> current = head; while(current!=null){ if(Objects.equals(current.key,key)){ V oldValue = current.value; current.value = value; return oldValue; } current = current.next; } // key 不存在,创建新节点,插入链表头部 Node<K,V> newNode = new Node<>(key,value); newNode.next = head; table[index] = newNode; size++; if(size>table.length*LOAD_FACTOR){ resize(); } return null; } // get 方法 public V get(K key){ int index = hash(key); Node<K,V> current = table[index]; while(current!=null){ if(Objects.equals(current.key,key)){ return current.value; } current = current.next; } return null; } // remove 方法:删除键值对 public V remove(K key) { int index = hash(key); Node<K,V> current = table[index]; Node<K,V> prev = null; while(current!=null){ if(Objects.equals(current.key,key)){ if(prev == null){ table[index] = current.next; } else{ prev.next = current.next; } } prev = current; current = current.next; } return null; } @SuppressWarnings("unchecked") public void resize(){ int oldCapacity = table.length; int newCapacity = oldCapacity*2; Node<K,V>[] newTable = (Node<K,V>[]) new Node[newCapacity]; for(int i=0;i<oldCapacity;i++){ Node<K,V> current = table[i]; while(current!=null){ Node<K,V> next = current.next; int newIndex = Math.abs(current.key.hashCode())%newCapacity; current.next = newTable[newIndex]; newTable[newIndex] = current; current=next; } } table = newTable; } }
设计哈希函数
目的:设计一个好的哈希函数,确保哈希值均匀分布,避免大量哈希冲突。
常用方法:
取模法:将键的哈希值对哈希表的大小取模。
// 简单的取模哈希 public static int modHash(int key, int tableSize) { return key % tableSize; } // 处理负数的取模 public static int modHashSafe(int key, int tableSize) { return Math.abs(key) % tableSize; } // 位运算取模(当 tableSize 是 2 的幂时) public static int modHashBit(int key, int tableSize) { // tableSize 必须是 2 的幂,如 16, 32, 64... return key & (tableSize - 1); // 位运算,更快 }字符串哈希:对于字符串,可以逐个字符进行哈希,然后结合前一个字符的哈希值。
// 1. 简单累加法(容易冲突) public static int simpleStringHash(String str, int tableSize) { int hash = 0; for (int i = 0; i < str.length(); i++) { hash += str.charAt(i); // 简单累加字符的 ASCII 值 } return Math.abs(hash) % tableSize; } // 2. 多项式哈希(Java String 的 hashCode 方法) public static int polynomialHash(String str) { int hash = 0; for (int i = 0; i < str.length(); i++) { // s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] hash = 31 * hash + str.charAt(i); } return hash; } // 3. 带取模的多项式哈希 public static int polynomialHashWithMod(String str, int tableSize) { int hash = 0; for (int i = 0; i < str.length(); i++) { hash = (31 * hash + str.charAt(i)) % tableSize; } return Math.abs(hash) % tableSize; } // 4. BKDR 哈希算法(常用) public static int bkdrHash(String str, int tableSize) { int hash = 0; int seed = 131; // 31 131 1313 13131 131313 等质数 for (int i = 0; i < str.length(); i++) { hash = hash * seed + str.charAt(i); } return Math.abs(hash) % tableSize; } // 5. DJB2 哈希算法 public static int djb2Hash(String str, int tableSize) { int hash = 5381; for (int i = 0; i < str.length(); i++) { hash = ((hash << 5) + hash) + str.charAt(i); // hash * 33 + c } return Math.abs(hash) % tableSize; } // 6. SDBM 哈希算法 public static int sdbmHash(String str, int tableSize) { int hash = 0; for (int i = 0; i < str.length(); i++) { hash = str.charAt(i) + (hash << 6) + (hash << 16) - hash; } return Math.abs(hash) % tableSize; }
更多推荐



所有评论(0)