extendible Hash

原理

静态散列要求桶的数目始终固定,那么在确定桶数目和选择散列函数时,如果桶数目过小,随着数据量增加,性能会降低;如果留一定余量,又会带来空间的浪费;或者定期重组散列索引结构,但这是一项开销大且耗时的工作。为了应对这些问题,为此提出了几种动态散列(dynamic

hashing)技术,可扩展动态散列(extendable hashing)便是其一。

一、可扩展动态散列

img

A)用一个数组来存储桶指针的目录,数组的位数为2的D次方,桶的容量为2的L次方,D和L分别称为全局位深度和局部位深度。每次发生桶溢出时,溢出桶分裂,容量变为2的L+1次方,其它桶的容量保持不变,同时数据目录的深度变为D+1。扩展容量时,只是调整了局部的桶容量和目录的容量,性能开销比较小。

上图中,目录深度为2,目录项有4个。然后开始插入数据d1和d2,假定h(d1)=13、h(d2)=20,由于13=1101,且全局位深度为2,则根据后两位01确定应插入b桶,b桶有空间,可直接插入。20=10100,应插入a桶,但a桶以及满了,于是开始分裂,a桶的局部位深度变为3,容量扩展为8,如果扩展后的局部位深度超过了全局位深度,则全局位深度等于这个最大的局部位深度,于是全局位深度也随之变为3。

img

如上图所示,a桶分裂为a1、a2,目录变为三位,对原来a桶中的元素进行重组,由于目录位多了一位,要根据000、100来分别存储到a1、a2桶。虽然目录发生了翻倍,但未进行分裂的桶的局部深度仍然为2,所以会有多个目录项指向这些桶,比如001、101的后两位都是01,都指向b桶。

B)对于查找操作,根据当前的全局位深度,通过目录直接定位到桶地址,随后在桶内部逐一查找。

C)对于删除操作,与查找操作类似,删除元素后,如果发现桶变为空,可与其兄弟桶进行合并,并使局部位深度减一。如果所有的局部位深度都小于全局位深度,则目录数组也进行收缩。

二、静态散列与动态散列对比

与静态散列相比,动态散列的主要优势在于其性能不会随着记录数增长而下降,另外还具有最小的空间占用。缺点在于它会额外增加一次查询定位,因为在查询bucket本身前,需要先查找目录来定位bucket。

另一种动态散列技术-线性散列(linear hashing)可以避免额外的查询定位,但可能这种方式需要更多的溢出桶,日后学习。

三、顺序索引与散列的适用场景

每种索引结构都有其优缺点。如果是select * from a where b=c这样的定值查询,散列比顺序索引跟适合,顺序索引会随着记录数的增加而性能降低,散列则相对稳定。而对于where b>c and b

例子

img

img

img

img

这个扩容可能一次还解决不了问题。比如BUCKET SIZE是2.
有一个BUCKET里有 00110,00100(local depth 是2),你要插入的是00101
先扩容一次,LOCAL DEPTH 变为3了,但是3个元素的头3位都是001,所以还是挤在一起了。所以要用WHILE 循环。
直到有足够的空间可以放了为止。

实现

抽象类

template <typename K, typename V> class HashTable {
public:
  HashTable()a {}
  virtual ~HashTable() {}
  // lookup and modifier
  virtual bool Find(const K &key, V &value) = 0;
  virtual bool Remove(const K &key) = 0;
  virtual void Insert(const K &key,const V &value) = 0;
};

头文件


template <typename K, typename V>
class ExtendibleHash : public HashTable<K, V> {
  struct Bucket {
    explicit Bucket(int depth):localDepth(depth) {};
    int localDepth;
    std::map<K, V> items;
  };

public:
  // constructor
  ExtendibleHash(size_t size);
  // helper function to generate hash addressing
  size_t HashKey(const K &key) const;
  // helper function to get global & local depth
  int GetGlobalDepth() const;
  int GetLocalDepth(int bucket_id) const;
  int GetNumBuckets() const;
  // lookup and modifier
  bool Find(const K &key, V &value) override;
  bool Remove(const K &key) override;
  void Insert(const K &key, const V &value) override;

private:
  // add your own member variables here
  int getBucketIndex(const K &key) const;
  int globalDepth;
  size_t bucketMaxSize;
  int numBuckets;
  std::vector<std::shared_ptr<Bucket>> bucketTable;
  std::mutex mutex;
};
template <typename K, typename V>
class ExtendibleHash : public HashTable<K, V> {
  struct Bucket {
    explicit Bucket(int depth):localDepth(depth) {};
    int localDepth;
    std::map<K, V> items;
  };

public:
  // constructor
  ExtendibleHash(size_t size);
  // helper function to generate hash addressing
  size_t HashKey(const K &key) const;
  // helper function to get global & local depth
  int GetGlobalDepth() const;
  int GetLocalDepth(int bucket_id) const;
  int GetNumBuckets() const;
  // lookup and modifier
  bool Find(const K &key, V &value) override;
  bool Remove(const K &key) override;
  void Insert(const K &key, const V &value) override;

private:
  // add your own member variables here
  int getBucketIndex(const K &key) const;
  int globalDepth;
  size_t bucketMaxSize;
  int numBuckets;
  std::vector<std::shared_ptr<Bucket>> bucketTable;
  std::mutex mutex;
};

具体实现


/*
 * constructor
 * array_size: fixed array size for each bucket
 */
template <typename K, typename V>
ExtendibleHash<K, V>::ExtendibleHash(size_t size)
        :globalDepth(0), bucketMaxSize(size), numBuckets(1) {
  bucketTable.push_back(std::make_shared<Bucket>(0));
}

/*
 * helper function to calculate the hashing address of input key
 */
template <typename K, typename V>
size_t ExtendibleHash<K, V>::HashKey(const K &key) const {
  return std::hash<K>{}(key);
}

/*
 * helper function to return global depth of hash table
 * NOTE: you must implement this function in order to pass test
 */
template <typename K, typename V>
int ExtendibleHash<K, V>::GetGlobalDepth() const {
  return globalDepth;
}

/*
 * helper function to return local depth of one specific bucket
 * NOTE: you must implement this function in order to pass test
 */
template <typename K, typename V>
int ExtendibleHash<K, V>::GetLocalDepth(int bucket_id) const {
  if (!bucketTable[bucket_id] || bucketTable[bucket_id]->items.empty()) return -1;
  return bucketTable[bucket_id]->localDepth;
}

/*
 * helper function to return current number of bucket in hash table
 */
template <typename K, typename V>
int ExtendibleHash<K, V>::GetNumBuckets() const {
  return numBuckets;
}

/*
 * lookup function to find value associate with input key
 */
template <typename K, typename V>
bool ExtendibleHash<K, V>::Find(const K &key, V &value) {
  //https://en.cppreference.com/w/cpp/thread/mutex
  std::lock_guard<std::mutex> guard(mutex);

  auto index = getBucketIndex(key);
  std::shared_ptr<Bucket> bucket = bucketTable[index];
  if (bucket != nullptr && bucket->items.find(key) != bucket->items.end()) {
    value = bucket->items[key];
    return true;
  }
  return false;
}

/*
 * delete <key,value> entry in hash table
 * Shrink & Combination is not required for this project
 */
template <typename K, typename V>
bool ExtendibleHash<K, V>::Remove(const K &key) {
  std::lock_guard<std::mutex> guard(mutex);

  auto index = getBucketIndex(key);
  std::shared_ptr<Bucket> bucket = bucketTable[index];

  if (bucket == nullptr || bucket->items.find(key) == bucket->items.end()) {
    return false;
  }
  bucket->items.erase(key);
  return true;
}

template <typename K, typename V>
int ExtendibleHash<K, V>::getBucketIndex(const K &key) const {
  return HashKey(key) & ((1 << globalDepth) - 1);
}

/*
 * insert <key,value> entry in hash table
 * Split & Redistribute bucket when there is overflow and if necessary increase
 * global depth
 */
template <typename K, typename V>
void ExtendibleHash<K, V>::Insert(const K &key, const V &value) {
  std::lock_guard<std::mutex> guard(mutex);

  auto index = getBucketIndex(key);
  std::shared_ptr<Bucket> targetBucket = bucketTable[index];

  while (targetBucket->items.size() == bucketMaxSize) {
    if (targetBucket->localDepth == globalDepth) {
      size_t length = bucketTable.size();
      for (size_t i = 0; i < length; i++) {
        bucketTable.push_back(bucketTable[i]);
      }
      globalDepth++;
    }
    int mask = 1 << targetBucket->localDepth;
    numBuckets++;
    auto zeroBucket = std::make_shared<Bucket>(targetBucket->localDepth + 1);
    auto oneBucket = std::make_shared<Bucket>(targetBucket->localDepth + 1);
    for (auto item : targetBucket->items) {
      size_t hashkey = HashKey(item.first);
      if (hashkey & mask) {
        oneBucket->items.insert(item);
      } else {
        zeroBucket->items.insert(item);
      }
    }

    for (size_t i = 0; i < bucketTable.size(); i++) {
      if (bucketTable[i] == targetBucket) {
        if (i & mask) {
          bucketTable[i] = oneBucket;
        } else {
          bucketTable[i] = zeroBucket;
        }
      }
    }

    index = getBucketIndex(key);
    targetBucket = bucketTable[index];
  } //end while

  targetBucket->items[key] = value;
}

这里只给出桶溢出时的原理和实现,另外桶缩减到一定程度时也要合并,需要考虑这种情况

参考链接:https://www.jianshu.com/p/2d816f41966c

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值