展开哈希表而不重新哈希?

时间:2015-10-05 18:05:27

标签: c data-structures hashtable

我正在寻找一个hash table数据结构,不需要rehash进行扩展和缩小? Rehash是一项耗费CPU的工作。我想知道是否有可能以一种不需要rehash的方式设计哈希表数据结构?您之前听说过这样的数据结构吗?

3 个答案:

答案 0 :(得分:1)

这取决于你想要避免的。 Rehashing意味着重新计算哈希值。您可以通过将哈希值存储在哈希结构中来避免这种情况。将条目重新分配到重新分配的哈希表中可能更便宜(通常是单个模数或掩码操作),并且对于简单的哈希表实现几乎不可避免。

答案 1 :(得分:1)

  

不需要rehash进行扩展和缩小? Rehash是一项耗费CPU的工作。我想知道是否有可能以一种不需要rehash的方式设计哈希表数据结构?您之前听说过这样的数据结构吗?

这取决于你所说的" rehash":

  • 如果您只是意味着表级重新散列不应该在调整大小期间对每个键重新应用散列函数,那么对于大多数库来说这很容易:例如:将密钥及其原始(模前表大小)实际哈希值包装在一起la struct X { size_t hash_; Key key_ };,为哈希表库提供返回hash_的哈希函数,但比较函数比较{ {1}} s(取决于key_比较的复杂程度,您可以使用key_进行优化,例如hash_)。

    • 如果密钥的散列特别耗时(例如,长按键上的加密强度),这将对大多数情况有帮助。对于非常简单的散列(例如lhs.hash_ == rhs.hash_ && lhs.key_ == rhs.key_ s的直通),它会减慢你的速度并浪费你的记忆。
  • 如果你的意思是增加或减少内存存储的表级操作并重新索引所有存储的值,那么是 - 可以避免 - 但要这样做,你必须从根本上改变哈希表的工作方式,和正常的性能配置文件。讨论如下。

仅作为一个示例,您可以通过使自定义哈希表(C)具有int(最大为初始大小限制)来利用更典型的哈希表实现(让我们称之为H)。将H** p作为H的唯一实例,并简单地将操作/结果通过。如果表格超出该范围,则保持p[0]引用现有的H,同时创建第二个H哈希表,以便p[0]跟踪。事情开始变得冒险:

  • 要在C中搜索或删除,您的实施需要搜索p[1]然后p[1]并报告任何匹配

  • 要在C中插入新值,您的实施必须确认它不在p[0]中,然后插入p[0]

    • 与每个p[1](甚至可能用于其他操作),它可以选择性地将任何匹配 - 或任意insert条目迁移到p[0],以便逐渐p[1]清空;您可以轻松保证p[0]p[0]如此满之前为空(因此需要更大的表格)。如果p[1]为空,您可能希望p[0]保留简单的心理模型 - 很多选项。

一些现有的哈希表实现在迭代元素(例如GNU C ++ p[0] = p[1]; p[1] = NULL;)时非常有效,因为它们是所有值的单链表,而哈希表实际上只是一个集合指针(用C ++术语,迭代器)到链表中。这可能意味着,如果您的利用率低于某个阈值(例如,10%的加载因子),那么您就可以非常有效地将剩余元素迁移到较小的表中。

一些散列表使用这些技巧来避免在重新散列期间突然沉重的成本,而是在一些后续操作中更均匀地分散疼痛,避免可能会出现令人讨厌的延迟峰值。

某些实现选项仅对打开的闭合哈希实现有意义,或仅在键和/或值小或大并且取决于表是否嵌入时有用他们或指向他们。了解它的最佳方法是编码......

答案 2 :(得分:0)

假设你确实需要这个......这是可能的。在这里,我将给出一个可以构建的简单示例。

// Basic types we deal with
typedef uint32_t key_t;
typedef void *   value_t;
typedef struct 
{
    key_t key;
    value_t value;
} hash_table_entry_t;

typedef struct
{
    uint32_t initialSize;
    uint32_t size; // current max entries
    uint32_t count; // current filled entries
    hash_table_entry_t *entries;
} hash_table_t;

// Hash function depends on the size of the table
key_t hash(value_t value, uint32_t size)
{
    // Simple hash function that just does modulo hash table size;
    return *(key_t*)&value % size;
}

void init(hash_table_t *pTable, uint32_t initialSize)
{
    pTable->initialSize = initialSize;
    pTable->size = initialSize;
    pTable->count = 0;
    pTable->entries = malloc(pTable->size * sizeof(*pTable->entries));
    /// @todo handle null return;
    // Set to ~0 to signal invalid keys.
    memset(pTable->entries, ~0, pTable->size * sizeof(*pTable->entries));
}

void insert(hash_table_t *pTable, value_t val)
{
    key_t key = hash(val, pTable->size);
    for (key_t i = key; i != (key-1); i=(i+1)%pTable->size)
    {
        if (pTable->entries[i].key == ~0)
        {
            pTable->entries[i].key = key;
            pTable->entries[i].value = val;
            pTable->count++;
            break;
        }
    }

    // Expand when 50% full
    if (pTable->count > pTable->size/2)
    {
        pTable->size *= 2;
        pTable->entries = realloc(pTable->entries, pTable->size * sizeof(*pTable->entries));
        /// @todo handle null return;
        memset(pTable->entries + pTable->size/2, ~0, pTable->size * sizeof(*pTable->entries));
    }
}

_Bool contains(hash_table_t *pTable, value_t val)
{
    // Try current size first
    uint32_t sizeToTry = pTable->size;
    do
    {
        key_t key = hash(val, sizeToTry);
        for (key_t i = key; i != (key-1); i=(i+1)%pTable->size)
        {
            if (pTable->entries[i].key == ~0)
                break;
            if (pTable->entries[i].key == key && pTable->entries[i].value == val)
                return true;
        }

        // Try all previous sizes we had. Only report failure if found for none.
        sizeToTry /= 2;
    } while (sizeToTry != pTable->initialSize);
    return false;
}

这个想法是哈希函数取决于表的大小。更改表格大小时,不要重新更新当前条目。使用新的哈希函数添加新的。在读取条目时,您将尝试在此表中使用过的所有哈希函数。

这样,get() / contains()和类似的操作花费的时间越长,您扩展桌面的次数就越多,但您不会有大幅度的重复播放。我可以想象一些系统需要它。