【问题标题】:Extendible Hashing, doubling the size of an array of pointers可扩展散列,将指针数组的大小加倍
【发布时间】:2014-07-01 07:21:06
【问题描述】:

我正在尝试在 C++ 中实现可扩展散列

有一个结构体充当索引,它包含一个“桶”类型的数组

Bucket * bucket_pointers;

还有另一个结构,Bucket,它有一个数组,用来保存我的值

E values[N] = {};

我有一个或多或少的工作程序,但有一个问题:每次我将哈希表的大小加倍时,我都会将所有存储桶复制到一个新数组中(大小的两倍)

Index_0
Bucket <n= 3, local_depth=2, 0x100200000>
[12,4,,8,]
Index_1
Bucket <n= 0, local_depth=1, 0x100200028>
[,,,,]
Index_2
Bucket <n= 3, local_depth=2, 0x100200050>
[2,10,6,,]
Index_3
Bucket <n= 0, local_depth=1, 0x100200078>
[,,,,]

但是,地址为 0x100200078 的 Bucket 实际上应该指向地址为 0x100200028 的 Bucket,即两个索引(1 和 3)应该指向同一个 Bucket。

我在这里决定是拆分存储桶还是将索引大小增加一倍...

while (!bucket_pointers[h%index_size].append(e)){ 
    if(bucket_pointers[h%index_size].local_depth<global_depth){
        split(hashValue);
    }
    else if(bucket_pointers[h%index_size].local_depth==global_depth){
        resize();
    }
}

我目前正在像这样将数组的大小增加一倍:

for (size_t i = 0; i < index_size;  ++i){
            for (size_t j = 0; j < bucket_pointers[i].n;  ++j){ 
                newBucket_pointers[i] = bucket_pointers[i];
                newBucket_pointers[i+index_size] = bucket_pointers[i];
            }
    }

【问题讨论】:

  • 我认为我们需要查看更多代码。您是否考虑过将 std::vector 用于可扩展哈希表?它会为您完成所有工作。
  • 嗨,理查德,感谢您的评论,我已经编辑了答案以添加更多代码。好吧,这当然会更容易!但是,我正在尝试学习 C++,所以在这种情况下,这不是一个选择。
  • 也许我没有正确阅读代码,或者你只是在做这件事有点不同,但至少出现你遇到了一个相关的问题(并且可能还不知道)是您在新表中重新安装节点的方式。节点的最终哈希索引(通常)基于 表大小 的模数。如果要将表的大小加倍,则需要重新散列每个节点以计算(希望有 50% 的可能性)给定节点在扩展表中具有新家的可能性。批量复制数组是行不通的(再次,如果我在你的代码上留了空间,我深表歉意)。
  • 我同意这一点。我还关心一个桶中的固定数量的地方。您是否打算在每次存储桶填满时添加索引?
  • 嗯,发生了两件事:我要么调整我的哈希表的大小(加倍),要么我正在拆分一个桶(这也是我重新散列每个节点的地方。确切地说,那是我是如何理解“可扩展散列”的。有一个固定的桶大小,每次一个桶填满时,我要么:拆分桶,要么加倍我的哈希表,然后拆分桶(取决于全局和本地深度)

标签: c++ arrays pointers hashtable


【解决方案1】:

正如@user2079303 已经指出的那样,你想要的是一个 Bucket** 数组。

让我用一些图像来澄清这一点:

Extendible-hashing explained

要记住一件事,以防 Bucket** index = new Bucket*[&lt;size_here&gt;] 让您感到困惑, 假设你想做一个简单的int-array。 你会这样做:

int* nums = new int[5];

想象一下减少右侧 * 符号的数量,因为 那就是定义内容类型应该是什么。因此,您想要存储的只是存储桶的地址。因此,index 包含 1 个或多个指向存储桶的 指针

希望对你有帮助!

【讨论】:

    【解决方案2】:

    请注意,Bucket * bucket_pointers; 不是一个 Bucket 指针数组,正如其名称所暗示的那样。它是一个指向 Bucket 的指针(具体来说是 Buckets 数组中的第一个 Bucket)。

    因此,当您将存储桶数组复制到另一个存储桶时,您最终会得到相同的存储桶副本,每个存储桶都有自己的 values 数组。

    newBucket_pointers[i] = bucket_pointers[i];
    newBucket_pointers[i+index_size] = bucket_pointers[i];
    

    如果您希望newBucket_pointers[i]newBucket_pointers[i+index_size] 成为指向相同Bucket 的指针,那么bucket_pointers(和newBucket_pointers)的类型实际上应该是Bucket**。那么bucket_pointers 是指向Bucket* 的指针,bucket_pointers[i] 是指向Bucket 的指针。这样bucket_pointers[i]newBucket_pointers[i]newBucket_pointers[i+index_size] 将指向同一个桶。我推荐使用std::vector&lt;Bucket*&gt; bucket_pointers 来代替,以便于内存管理。

    如果相反,您打算像现在一样复制存储桶,但它们的 values 成员指向共享数组,那么您可以保持 bucket_pointers 原样,您需要更改 values 的类型指向一个指针并分别分配数组。如果您想以这种方式共享数组,您可能应该使用shared_ptr 以使最终的释放更容易。

    【讨论】:

    • 这听起来不错!但是,我仍然不太确定如何实现这一点。我是否必须在 Bucket 对象中添加一个指针,该指针要么指向同一个 Bucket 对象中的 values 数组,要么指向另一个 Bucket 对象中的另一个 values 数组?它是一个指向值数组的指针,像这样?? E (*pointer)[N]; // A pointer to an array of values of type E
    • @intagli:实际上,既然我再次阅读了您的问题,我不确定我是否理解正确。我假设您想要复制存储桶并让它们共享相同的 values 数组,但现在我认为您想要复制指向存储桶的指针。你能确认你打算做什么吗?
    • 好吧,我的意图是加倍我的哈希表,它应该由存储桶或指向这些存储桶的指针组成...即,哈希表索引 1 引用/包含存储桶,哈希表索引 3 应该指向同一个桶。因此,如果我要添加一个值,它应该进入同一个存储桶......直到我拆分一个存储桶,然后我将指针分配到旧存储桶和新存储桶,并相应地重新散列值。至少这是计划。
    • @intagli:坦率地说,我不熟悉可扩展散列,但我编辑了我的答案来解释如何让bucket_pointers 的索引指向同一个存储桶。本质上,您需要一个指针数组而不是 Buckets 数组。
    【解决方案3】:

    我在下面包含了一些代码,它们作为一个非常简单的哈希表执行。它仅用于教学目的,并不足以在实际应用程序中使用。在现实生活中使用内置的 std::unordered_set 效果更好。

    我通过使用链表作为可以根据需要扩展的存储桶来避免更改存储桶大小的需要。

    这个例子对你走上正轨有帮助吗?

    #include <iostream>
    #include <array>
    #include <list>
    #include <string>
    #include <cassert>
    
    
    class CTable
    {
    public:
        void Add(const std::string &sKey, int nVal);
        int  Find(const std::string &sKey);
    
    protected:
        size_t Index(const std::string &sKey);
    
    private:
        struct SData
        {
            SData(const std::string &s, int n)
            : sKey(s)
            , nVal(n)
            {
            }
            std::string sKey;
            int         nVal;
        };
        typedef std::list<SData> Bucket_t;
        enum { nBuckets = 24 };
        typedef std::array<Bucket_t, nBuckets> Table_t;
        Table_t m_table;
    
        const SData *Lookup(const Bucket_t &b, const std::string &sKey);
    };
    
    void CTable::Add(const std::string &sKey, int nVal)
    {
        size_t nIndex = Index(sKey);
        const SData *p = Lookup(m_table.at(nIndex), sKey);
        if (p)
            throw std::runtime_error("duplicate key");
        m_table.at(nIndex).push_back(SData(sKey, nVal));
    }
    
    int CTable::Find(const std::string &sKey)
    {
        size_t nIndex = Index(sKey);
        const SData *p = Lookup(m_table.at(nIndex), sKey);
        if (p)
            return p->nVal;
        else
            throw std::runtime_error("not found");
    }
    
    size_t CTable::Index(const std::string &sKey)
    {
        return std::hash<std::string>()(sKey) % m_table.size();
    }
    
    const CTable::SData *CTable::Lookup(const CTable::Bucket_t &b, 
                                        const std::string &sKey)
    {
        for (const SData &s : b)
            if (s.sKey == sKey)
                return &s;
        return nullptr;
    }
    
    
    int main() 
    {
        CTable t;
    
        t.Add("one", 1);
        t.Add("two", 2);
        t.Add("three", 3);
    
        assert(2 == t.Find("two"));
    
        try
        {
            t.Find("four");
            assert(false);
        }
        catch (std::exception &)
        {
        }
        try
        {
            t.Add("two", 3);
            assert(false);
        }
        catch (std::exception &)
        {
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-22
      • 2014-04-10
      • 1970-01-01
      • 2018-06-23
      • 2021-01-08
      • 1970-01-01
      相关资源
      最近更新 更多