如果链表太慢,通常的答案是实现哈希表。数据结构和算法有很多很多可能的变化。我将只描述开放的“单一”哈希,因为这是我最熟悉的变体。
因此,对于开放哈希表,该表只是一个数组(“封闭”哈希也有一个数组,但每个元素都是一个链表)。出于性能原因,我们希望阵列最多为半满。并且在最大容量时,已填满的表实际上仍然有一个空槽,因为这简化了算法。
我们还需要一个接受键值类型并返回整数的哈希函数。很难预测散列函数在聚集键和整体性能方面的表现。因此,只需确保它是一个独立的功能,以后可以轻松更改。它可以像移动所有字节并将它们加在一起一样简单。
int hash (char *key, int key_length, int table_size)
{
int ret, i;
for (i=0, ret=0; i < key_length; i++)
{
ret += key[i] << i;
}
return abs(ret) % table_size;
}
查表函数使用散列函数来决定从哪里开始查找数组。如果在那里找不到键(通过memcmp()
对实际搜索键和存储在表中该位置的键执行 a 来确定),它会查看每个连续的键,从数组的末尾回绕到开头,并且如果发现空表元素,则声明失败。
#define RETURN_TABLE_I_IF_EQUAL_KEY_OR_EMPTY \
if (memcmp(table + i, &key, sizeof key) == 0 || (key_type)table[i] == 0) \
return table + i;
key_value_pair *hash_lookup(key_value_pair *table, int table_size, key_type key)
{
int h, i;
h = hash(&key, sizeof key, table_size);
i = h;
RETURN_TABLE_I_IF_EQUAL_KEY_OR_EMPTY
for ( ; i < table_size; i++)
RETURN_TABLE_I_IF_EQUAL_KEY_OR_EMPTY
for (i=0; i < h; i++)
RETURN_TABLE_I_IF_EQUAL_KEY_OR_EMPTY
return NULL;
}
在此之前,我们还需要一个函数来处理一些怪癖。它可以返回一个 NULL 指针,这表明不仅没有找到键,而且表本身已满。一个过满的表,这实际上意味着“完全满”,但我们之前决定一个“满”的表应该真的有一个空元素。这意味着两个for
循环都不应该运行完成;当它找到一个空表位置时,这是一个失败。对于过满的表,它必须在发现键不存在之前扫描整个表,从而完全失去使用散列的大部分性能优势。
查找函数还可以返回一个指向空槽的有效指针。这也是找值失败,但不是错误。如果是第一次添加键/值对,这将是存储它的槽。
或者它可以返回一个指向所需表格元素的指针。这将比线性搜索更快,无论是数组还是链表。
从表中删除一个键需要我们填写序列中空出的位置。有几个选项。
如果您不担心表空间不足(它设置得非常大,并且可以控制生命周期和使用情况),您可以使用已删除的特殊键覆盖条目,与空键不同。
或者,如果您也想回收空间,则需要查找密钥,然后扫描“链”的其余部分(直到下一个空槽(包括环绕)的密钥序列)并移动具有匹配哈希的最后一个键到要删除的键的位置。然后用空键覆盖这个移动的键/值的位置。....哎呀!必须对最后一个匹配键重复此过程,直到我们真正清除链中的最后一个键。(我现在需要在我的实施中解决这个问题!....)