哈希表
哈希表是什么?
哈希表(Hash Table)是一种数据结构,用于快速存储和查找数据。它通过将键(key)映射到数组中的索引位置来实现高效的查找、插入和删除操作。 乍一看不明白很正常,如果你学过数据库就会觉得很熟悉,哈希表的功能拓展一下不就是数据库中的表。
以下是哈希表与数据库之间概念对应的对比表格:
概念 | 哈希表 | 数据库 |
---|---|---|
数据存储结构 | 键(Key)与值(Value) | 主键(Primary Key)、字段(Column) |
数据操作 | 插入(Insert)、查找(Lookup)、删除(Delete) | 插入(INSERT)、查询(SELECT)、删除(DELETE) |
数据关系 | 独立性 | 支持关系(Relation),通过外键建立关联 |
数据一致性与事务 | 无事务支持 | 支持ACID特性,具备事务管理 |
查询能力 | 简单查找 | 支持复杂查询,使用SQL |
数据持久性 | 内存存储 | 持久化存储,通常存储在磁盘上 |
一维的哈希,组成二维的表,二维表组成关系数据库。而且数据库里面也有基于这个数据结构的哈希索引。
再看一下sql语句,是不是两个重合了。
String sql = "INSERT INTO teacher(id, username, password) VALUES (?, ?, ?)";
使用数组和链表实现哈希表
1. 数组实现
我们可以使用一个简单的数组来存储键值对。每个元素将是一个链表,用于处理冲突。在哈希表中,我们将数组中的每个空位称为桶(桶就像一个容器,可以存放多个元素。在哈希表中,多个键可能会映射到同一个哈希值,导致冲突。每个桶就是一个用来存储这些冲突元素的地方。)
数组实现的哈希表类
class Node {
String key;
int value;
Node next;
public Node(String key, int value) {
this.key = key;
this.value = value;
this.next = null;
}
}
class HashTable {
private Node[] table;
private int size;
public HashTable(int size) {
this.size = size;
this.table = new Node[size]; // 创建一个数组
}
private int hashFunction(String key) {
return key.hashCode() % size; // 使用哈希函数
}
public void insert(String key, int value) {
int index = hashFunction(key);
Node newNode = new Node(key, value);
// 插入到链表头
newNode.next = table[index];
table[index] = newNode;
}
public Integer search(String key) {
int index = hashFunction(key);
Node current = table[index];
while (current != null) {
if (current.key.equals(key)) {
return current.value;
}
current = current.next;
}
return null; // 未找到
}
public boolean delete(String key) {
int index = hashFunction(key);
Node current = table[index];
Node prev = null;
while (current != null) {
if (current.key.equals(key)) {
if (prev != null) {
prev.next = current.next; // 删除节点
} else {
table[index] = current.next; // 删除头节点
}
return true;
}
prev = current;
current = current.next;
}
return false; // 未找到
}
}
2. 使用链表实现
假设我们不使用数组,而是使用链表实现哈希表的所有功能。这种方法可能不是最优的,但可以帮助理解哈希表的基本操作。
链表实现的哈希表类
class LinkedList {
private Node head;
public void insert(String key, int value) {
Node newNode = new Node(key, value);
newNode.next = head;
head = newNode;
}
public Integer search(String key) {
Node current = head;
while (current != null) {
if (current.key.equals(key)) {
return current.value;
}
current = current.next;
}
return null; // 未找到
}
public boolean delete(String key) {
Node current = head;
Node prev = null;
while (current != null) {
if (current.key.equals(key)) {
if (prev != null) {
prev.next = current.next; // 删除节点
} else {
head = current.next; // 删除头节点
}
return true;
}
prev = current;
current = current.next;
}
return false; // 未找到
}
}
3. 使用示例
可以使用以上代码创建一个哈希表并进行基本操作:
public class Main {
public static void main(String[] args) {
HashTable ht = new HashTable(10);
ht.insert("apple", 1);
ht.insert("banana", 2);
System.out.println("Searching for 'apple': " + ht.search("apple")); // 输出: 1
ht.delete("apple");
System.out.println("Searching for 'apple' after deletion: " + ht.search("apple")); // 输出: null
// 使用链表实现
LinkedList list = new LinkedList();
list.insert("orange", 3);
System.out.println("Searching for 'orange': " + list.search("orange")); // 输出: 3
}
}
为什么我们需要使用哈希表(哈希表的实际应用)
哈希表在实际中的应用非常广泛,涵盖了各个领域。下面列出几个具体的实例及其对应的功能:
2. 数据去重
- 功能:在处理大量数据时,通过哈希表快速判断数据是否已存在,避免重复。
- 实例:
- Twitter:使用哈希表来快速去重用户提交的内容,如推文和图片,确保内容唯一性。
2. 编译器
- 功能:存储和查找编程语言中的标识符及其相关信息。
- 实例:
- GCC (GNU Compiler Collection):在编译过程中使用哈希表来维护符号表,存储变量、函数等标识符信息。
- GCC (GNU Compiler Collection):在编译过程中使用哈希表来维护符号表,存储变量、函数等标识符信息。
3. 数据库索引
- 功能:加速数据库查询操作,通过哈希表快速找到记录的位置。
- 实例:
- MySQL:使用哈希索引来加速某些类型的查询,特别是针对唯一键的查找。
4. 词频统计
- 功能:在文本处理中,通过哈希表快速统计词频,进行文本分析。现在比较火的深度学习自然语言处理
- 实例:
- NLTK (Natural Language Toolkit):一个用于自然语言处理的库,使用哈希表来统计词频、构建词典等。
5. 在线购物车
- 功能:管理用户购物车中的项目,快速添加、删除和查找产品。
- 实例:
- Amazon:使用哈希表来管理购物车中的物品,确保快速响应用户的添加和删除操作。
6. 域名解析系统(DNS)
- 功能:将域名映射到对应的 IP 地址,使用哈希表实现快速查找。
- 实例:
- Google DNS:提供快速和可靠的域名解析服务,背后使用哈希表来加速域名到 IP 地址的映射过程。
哈希冲突
哈希冲突(Hash Collision)是指在使用哈希表时,不同的输入数据通过哈希函数得到相同的哈希值(也称为哈希码)的现象。由于哈希表的基本原理是在一定范围内将数据映射到哈希值,因此哈希冲突是不可避免的,即使在理想情况下也会发生。
原因
哈希冲突发生的主要原因是因为哈希函数将一个非常大的数据集映射到一个固定长度的哈希值空间。因为这个空间是有限的,所以当数据量足够大时,不同的数据可能会被映射到同一个哈希值。
处理哈希冲突的方法
为了解决哈希冲突,通常有以下几种常见的方法:
-
开放寻址法(Open Addressing)
- 线性探测(Linear Probing):当发生冲突时,按照固定的步长(通常是1)向前查找下一个空闲位置。
- 二次探测(Quadratic Probing):与线性探测类似,但步长是二次方序列(1, 4, 9, …),以减少聚集现象。
- 双重哈希(Double Hashing):使用第二个哈希函数生成探测序列,以解决一次哈希函数的局限性。
-
链地址法(Chaining)
- 链表法:每个哈希表的槽位存储一个链表,当发生冲突时,将数据插入到对应槽位的链表中。
- 树状结构:将链表替换为其他数据结构,如平衡树,以加速查找操作。
示例
假设我们有一个简单的哈希函数 h(x) = x % 10
,将整数映射到哈希表中。对于输入数据 15
和 25
,计算得到的哈希值都是 5
。如果采用链地址法处理冲突,哈希表中槽位 5
将存储一个链表,其中包含元素 15
和 25
。
持续更新中~~ 欢迎评论留言
- 从0开始的算法(数据结构和算法)基础(一)
- 从0开始的算法(数据结构和算法)基础(二)
- 从0开始的算法(数据结构和算法)基础(三)
- 从0开始的算法(数据结构和算法)基础(四)