哈希表:高效数据结构背后的工作原理

作者:张家界麻将开发公司 阅读:33 次 发布时间:2023-06-15 07:46:44

摘要:哈希表:高效数据结构背后的工作原理哈希表是实现字典和符号表的基础数据结构之一,被广泛应用于算法和程序设计中。在哈希表中,键(key)和值(value)之间的映射关系通过散列函数(hash function)进行计算和存储。相比于传统数据结构,在哈希表中查找和插入数据的时间复杂...

哈希表:高效数据结构背后的工作原理

哈希表:高效数据结构背后的工作原理

哈希表是实现字典和符号表的基础数据结构之一,被广泛应用于算法和程序设计中。在哈希表中,键(key)和值(value)之间的映射关系通过散列函数(hash function)进行计算和存储。相比于传统数据结构,在哈希表中查找和插入数据的时间复杂度是常数级别的,因此具有高效的特点。本文将探讨哈希表的工作原理、关键概念和实现方法。

一、散列函数

散列函数是哈希表的核心组成部分,它将任意长度的数据(例如键值对)映射到固定大小的地址空间内。这个映射过程被称为散列(hashing),散列函数将输入数据映射为哈希表中的索引值,这个索引值也称为桶(bucket)。

由于哈希表的索引值是有限的,因此散列函数的映射存在概率冲突的风险。即不同的数据可能映射到同一个桶中,这个现象称为哈希冲突(hash collision)。解决哈希冲突的方法有多种,后文中会逐一介绍。

常见的散列函数有多种,其中最简单的是除法散列法(division hashing)。与取余操作不同的是,除法散列法是将输入数据除以某个质数,然后取余数作为索引值。这个过程可以用以下公式表示:

hash(x) = x % p

其中,x是输入数据,p是质数。这个散列函数被广泛应用于实际开发中。

另一种常见的散列函数是乘法散列法(multiplicative hashing),这个散列函数由Knuth提出。对于一个不超过w位的整数x,乘法散列法的计算公式为:

hash(x) = (ax mod 2^w ) >> (w - r)

其中a是一个介于1和2^w-1之间的整数,r是哈希表的大小。这个散列函数的优点是可以令哈希冲突的概率尽可能小,并且计算速度较快。

二、哈希冲突

哈希冲突是哈希表面临的一个重要问题,也是哈希表有效性的重要指标之一。产生哈希冲突的原因是散列函数映射的地址空间是有限的,当哈希表中的元素数量增多时,不可避免地会出现数据映射到同一个桶的情况。为了保证查询的正确性和效率,我们需要尽可能地避免哈希冲突的发生。

解决哈希冲突的方法有多种,以下是其中的几种:

1. 拉链法(Chaining)

拉链法是一种最基本的哈希冲突解决方法,它通过在哈希表中的每一个桶中维护一个单向链表,将冲突的元素放置在同一桶内。这个过程可以用以下伪代码表示:

class Node:

def __init__(self, key, val):

self.key, self.val = key, val

self.next = None

class HashTable:

def __init__(self, capacity):

self.capacity = capacity

self.buckets = [None] * capacity

def put(self, key, val):

index = self._hash(key)

node = self.buckets[index]

while node:

if node.key == key:

node.val = val

return

node = node.next

new_node = Node(key, val)

new_node.next = self.buckets[index]

self.buckets[index] = new_node

def get(self, key):

index = self._hash(key)

node = self.buckets[index]

while node:

if node.key == key:

return node.val

node = node.next

raise KeyError(key)

在哈希表中插入元素时,我们首先计算元素的哈希值,然后将这个元素插入到哈希表中对应的桶中。如果这个桶之前已经有了一些元素,则将这些元素按照顺序连接成一个单向链表。

在查询一个元素时,我们首先计算这个元素的哈希值,然后遍历哈希表中对应的桶,逐个比较桶中元素的键值与查询键值是否相同。如果相同,则返回对应的值,否则抛出KeyError异常。

拉链法的优点是实现简单,而且可以动态扩展哈希表的大小,因此通常被广泛应用于实际开发中。它的缺点是会占用更多的空间,并且在删除元素时需要对链表进行遍历和修改。

2. 线性探测法(Linear Probing)

线性探测法是另一种常用的哈希冲突解决方法,它可以将发生冲突的元素依次放置到哈希表中的下一个空闲位置,从而避免链表等数据结构的使用。

当我们在哈希表中插入一个元素时,如果这个元素映射到的桶已经被占用,我们就从当前位置开始,依次查找下一个空桶,将元素插入到这个空桶中。这个过程可以用以下伪代码表示:

class HashTable:

def __init__(self, capacity):

self.capacity = capacity

self.buckets = [None] * capacity

self.size = 0

def put(self, key, val):

if self.size == self.capacity:

raise Exception("HashTable is full")

index = self._hash(key)

while self.buckets[index] is not None:

if self.buckets[index][0] == key:

self.buckets[index][1] = val

return

index = (index + 1) % self.capacity

self.buckets[index] = (key, val)

self.size += 1

def get(self, key):

index = self._hash(key)

while self.buckets[index] is not None:

if self.buckets[index][0] == key:

return self.buckets[index][1]

index = (index + 1) % self.capacity

raise KeyError(key)

当我们在哈希表中查询一个元素时,首先计算元素的哈希值,然后依次查找哈希表中对应的桶,判断桶中元素的键值是否等于查询键值。如果当前桶不是我们要找的元素,就继续检查下一个桶,直到找到元素或者遍历完整个哈希表。

线性探测法的优点是可以避免链表等数据结构的使用,并且可以减少哈希表占用的空间。缺点是容易产生聚集效应(clustering),即存在元素总是被插入到同一区域的情况,从而导致查询的效率降低。

三、哈希表的实现

哈希表作为一种常见的数据结构,其实现方式有多种,常见的包括动态哈希表、可扩展哈希表和动态调整阈值哈希表等。

动态哈希表

动态哈希表是一种最基本的哈希表实现方法,它的特点在于可以动态增加和减少哈希表的大小,从而适应不同的场景需求。

动态哈希表最简单的实现方式是拉链法,它通过在每个桶中维护一个链表来处理哈希冲突。这个过程中,我们需要记录当前哈希表的元素个数和桶的数量,当元素个数超过桶的数量时,我们需要重新分配桶的数量。具体的实现过程如下:

class Node:

def __init__(self, key, val):

self.key, self.val = (key, val)

self.next = None

class HashTable:

def __init__(self, capacity=8, ratio=0.75):

self.capacity = capacity

self.ratio = ratio

self.threshold = int(capacity * ratio)

self.buckets = [None] * capacity

self.size = 0

def put(self, key, val):

if self.size + 1 > self.threshold:

self._resize()

index = self._hash(key)

node = self.buckets[index]

while node:

if node.key == key:

node.val = val

return

node = node.next

new_node = Node(key, val)

new_node.next = self.buckets[index]

self.buckets[index] = new_node

self.size += 1

def get(self, key):

index = self._hash(key)

node = self.buckets[index]

while node:

if node.key == key:

return node.val

node = node.next

raise KeyError(key)

def _resize(self):

new_buckets = [None] * self.capacity * 2

for node in self:

index = self._hash(node.key, len(new_buckets))

new_node = Node(node.key, node.val)

new_node.next = new_buckets[index]

new_buckets[index] = new_node

self.buckets = new_buckets

self.capacity *= 2

self.threshold = int(self.capacity * self.ratio)

def _hash(self, key, capacity=None):

if capacity is None:

capacity = self.capacity

return hash(key) % capacity

def __iter__(self):

for node in self.buckets:

while node:

yield node

node = node.next

在这个实现中,我们首先定义了一个Node类,用于存储元素的键值对;然后定义了HashTable类,包含put、get和resize等方法。

在插入一个元素时,我们首先计算元素的哈希值,然后在哈希表中的对应桶中查找元素。如果这个桶中不存在元素,则将元素插入到这个桶中。如果这个桶中已经存在元素,则遍历链表中的元素,查找与当前元素键值相同的节点,如果找到了,则更新值;否则,将元素插入到链表的头部。

在查询一个元素时,我们也首先计算元素的哈希值,然后在哈希表中的对应桶中查找元素。如果找到了节点,则返回值;否则,抛出KeyError异常。

当哈希表中的元素数量达到一个阈值时,我们需要调整哈希表的大小。具体操作是创建一个新的哈希表,然后将旧表中的元素全部复制到新表中。如果新哈希表的大小是原哈希表大小的两倍,则把所有元素移动到新的哈希表中。这个过程中,我们需要重新计算元素的哈希值,并映射到新表中的桶中。

可扩展哈希表

可扩展哈希表是一种另类的哈希表实现方式,它通过在哈希表的最左侧添加一位标志位,根据标志位将哈希表中的元素分配到不同的子哈希表中。这样,可扩展哈希表可以在哈希表元素数量增长时自动扩展哈希表大小,并且实现简单。

可扩展哈希表的基本思路是将哈希表分为2^k个桶,k为标志位的长度。然后,在哈希表中的每个桶中,再把哈希表分为2^(k-1)个子哈希表中,以此类推。这样,哈希表中的元素就可以被分配到不同的子哈希表中,从而实现动态调整。

在可扩展哈希表中,我们定义了一个Bucket类,用于存储多个子哈希表;一个Node类,用于存储单个哈希表元素。具体实现如下:

class Node:

def __init__(self, key, val):

self.key, self.val = key, val

class Bucket:

def __init__(self):

self.is_leaf = True

self.bits = 0

self.nodes = [None] * 2

self.capacity = 2

self.size = 0

def put(self, key, val):

index = self._hash(key)

node = self.nodes[index]

while node:

if node.key == key:

node.val = val

return

node = node.next

new_node = Node(key, val)

new_node.next = self.nodes[index]

self.nodes[index] = new_node

self.size += 1

self._resize()

def get(self, key):

index = self._hash(key)

node = self.nodes[index]

while node:

if node.key == key:

return node.val

node = node.next

raise KeyError(key)

def _resize(self):

if self.size / self.capacity < 0.5:

return

new_capacity = self.capacity * 2

new_nodes = [None] * new_capacity

for node in self:

index = node.key % new_capacity

new_node = Node(node.key, node.val)

new_node.next = new_nodes[index]

new_nodes[index] = new_node

self.nodes = new_nodes

self.capacity = new_capacity

def _hash(self, key):

return (key >> self.bits) & 1

def __iter__(self):

if not self.is_leaf:

for node in self.nodes[0]:

if node is not None:

yield from node

for node in self.nodes[1]:

if node is not None:

yield from node

else:

for node in self.nodes:

if node is not None:

yield node

在这个实现中,我们首先定义了一个Node类,用于存储单个哈希表元素;然后定义了一个Bucket类,用于存储哈希表中的多个子哈希表。

在插入一个元素时,我们首先计算元素的哈希值,然后根据标志位将元素分配到不同的子哈希表中。如果当前子哈希表中已经存在了对应键值的元素,则更新它的值;否则,将元素插入到该子哈希表中的链表中。当子哈希表的元素数量达到一定阈值时,我们需要重新分配该子哈希表中的元素,同时将该子哈希表拆分成两个,以此类推。

在查询一个元素时,我们也首先计算元素的哈希值,然后根据标志位将元素定位到对应子哈希表中,依次查找该子哈希表中的链表,寻找与之匹配的元素。如果找到了元素,则返回它的值;否则,抛出KeyError异常。

四、总结

哈希表是一种高效的数据结构,能够快速地实现数据的查找、插入和删除。哈希表的实现方式有多种,常见的包括拉链法、线性探测法、可扩展哈希表和动态调整阈值哈希表等。不同的实现方式各有优缺点,我们需要根据场景需求来选择适合的实现方式

  • 原标题:哈希表:高效数据结构背后的工作原理

  • 本文链接:https:////zxzx/15584.html

  • 本文由深圳飞扬众网小编,整理排版发布,转载请注明出处。部分文章图片来源于网络,如有侵权,请与飞扬众网联系删除。
  • 微信二维码

    CTAPP999

    长按复制微信号,添加好友

    微信联系

    在线咨询

    点击这里给我发消息QQ客服专员


    点击这里给我发消息电话客服专员


    在线咨询

    免费通话


    24h咨询☎️:166-2096-5058


    🔺🔺 棋牌游戏开发24H咨询电话 🔺🔺

    免费通话
    返回顶部