数据结构与算法之散列表 面试高频 哈希冲突 / 扩容机制 考点

数据结构与算法阿木 发布于 4 天前 1 次阅读


摘要:散列表(Hash Table)是一种基于哈希函数的数据结构,它通过哈希函数将键映射到表中的一个位置,从而实现快速的查找、插入和删除操作。本文将围绕散列表的核心概念——哈希冲突和扩容机制,进行深入解析,并结合实际代码示例,帮助读者更好地理解这一面试高频考点。

一、

散列表是一种非常高效的数据结构,广泛应用于各种场景,如数据库索引、缓存、哈希集合等。散列表的设计和实现涉及到许多细节,其中哈希冲突和扩容机制是两个关键点。本文将详细探讨这两个问题,并通过代码示例进行说明。

二、哈希函数

哈希函数是散列表的核心,它将键(Key)映射到散列表中的一个位置(Index)。一个好的哈希函数应该具有以下特点:

1. 确定性:相同的键经过哈希函数处理后,总是映射到同一个位置。

2. 均匀分布:不同的键经过哈希函数处理后,应该均匀分布在整个散列表中,以减少冲突。

3. 快速计算:哈希函数的计算过程应该尽可能快,以提高散列表的效率。

以下是一个简单的哈希函数示例,它将整数键映射到散列表的大小范围内:

python

def simple_hash(key, table_size):


return key % table_size


三、哈希冲突

尽管哈希函数可以尽可能地减少冲突,但在实际应用中,冲突是不可避免的。当两个或多个键映射到同一个位置时,就发生了哈希冲突。

解决哈希冲突的方法主要有以下几种:

1. 开放寻址法(Open Addressing)

2. 链地址法(Chaining)

3. 双散列法(Double Hashing)

1. 开放寻址法

开放寻址法通过在散列表中寻找下一个空闲位置来解决冲突。以下是线性探测法(Linear Probing)的代码示例:

python

class HashTableOpenAddressing:


def __init__(self, size):


self.size = size


self.table = [None] size

def hash(self, key):


return key % self.size

def insert(self, key):


index = self.hash(key)


while self.table[index] is not None:


index = (index + 1) % self.size


self.table[index] = key

def search(self, key):


index = self.hash(key)


while self.table[index] is not None:


if self.table[index] == key:


return index


index = (index + 1) % self.size


return -1

def delete(self, key):


index = self.hash(key)


while self.table[index] is not None:


if self.table[index] == key:


self.table[index] = None


return


index = (index + 1) % self.size


2. 链地址法

链地址法通过在每个散列表位置维护一个链表来解决冲突。以下是链地址法的代码示例:

python

class HashTableChaining:


def __init__(self, size):


self.size = size


self.table = [None] size

def hash(self, key):


return key % self.size

def insert(self, key):


index = self.hash(key)


if self.table[index] is None:


self.table[index] = []


self.table[index].append(key)

def search(self, key):


index = self.hash(key)


if self.table[index] is not None:


for k in self.table[index]:


if k == key:


return index


return -1

def delete(self, key):


index = self.hash(key)


if self.table[index] is not None:


for i, k in enumerate(self.table[index]):


if k == key:


del self.table[index][i]


return


3. 双散列法

双散列法通过计算第二个哈希函数来解决冲突。以下是双散列法的代码示例:

python

class HashTableDoubleHashing:


def __init__(self, size):


self.size = size


self.table = [None] size

def hash1(self, key):


return key % self.size

def hash2(self, key):


return 1 + (key % (self.size - 1))

def rehash(self, old_index, key):


return (old_index + self.hash2(key)) % self.size

def insert(self, key):


index = self.hash1(key)


if self.table[index] is not None:


if self.table[index] == key:


return


index = self.rehash(index, key)


while self.table[index] is not None:


index = self.rehash(index, key)


self.table[index] = key

def search(self, key):


index = self.hash1(key)


if self.table[index] == key:


return index


index = self.rehash(index, key)


while self.table[index] is not None:


if self.table[index] == key:


return index


index = self.rehash(index, key)


return -1

def delete(self, key):


index = self.hash1(key)


if self.table[index] == key:


self.table[index] = None


return


index = self.rehash(index, key)


while self.table[index] is not None:


if self.table[index] == key:


self.table[index] = None


return


index = self.rehash(index, key)


四、扩容机制

随着散列表中元素的增多,冲突的概率也会增加,这会导致散列表的性能下降。为了解决这个问题,散列表需要具备扩容机制,即当散列表达到一定负载因子时,自动增加散列表的大小,并重新计算所有元素的哈希值。

以下是链地址法散列表的扩容机制代码示例:

python

class HashTableChaining:


...(省略其他方法)

def resize(self):


old_table = self.table


self.size = 2


self.table = [None] self.size


for key in old_table:


if key is not None:


for k in key:


self.insert(k)

def load_factor(self):


return len(self.table) / self.size


在上述代码中,`resize` 方法用于扩容散列表,`load_factor` 方法用于计算负载因子。当负载因子超过某个阈值时,可以调用 `resize` 方法进行扩容。

五、总结

散列表是一种高效的数据结构,但在设计和实现过程中需要考虑哈希冲突和扩容机制。本文通过代码示例详细解析了这两种机制,并介绍了三种解决哈希冲突的方法:开放寻址法、链地址法和双散列法。还展示了链地址法散列表的扩容机制。希望本文能帮助读者更好地理解散列表的相关知识,为面试做好准备。