打开寻址与单独链接

时间:2010-10-30 14:23:33

标签: hashtable hashmap hash-collision

当负载系数接近1时,哪种hashmap冲突处理方案更好,以确保最小的内存浪费?

我个人认为答案是使用线性探测进行开放式寻址,因为在发生冲突时不需要任何额外的存储空间。这是对的吗?

3 个答案:

答案 0 :(得分:1)

完整的散列图将降级为线性搜索,因此您需要将它们保持在90%以下。

对于使用较少内存的开放式寻址是正确的,链接将需要每个节点中的指针或偏移字段。

我已经创建了一个hasharray数据结构,用于何时需要非常轻量级的哈希表,这些哈希表不会有很多插入。为了保持较低的内存使用率,所有数据都嵌入在同一块内存中,HashArray结构在开始时,然后是两个用于散列和数组的数组。值。 Hasharray只能与查找键一起使用,存储在值中。

typedef uint16_t HashType;  /* this can be 32bits if needed. */
typedef uint16_t HashSize;  /* this can be made 32bits if large hasharrays are needed. */
struct HashArray {
  HashSize length;        /* hasharray length. */
  HashSize count;         /* number of hash/values pairs contained in the hasharray. */
  uint16_t value_size;    /* size of each value.  (maximum size of value 64Kbytes) */
  /* these last two fields are just for show, they are not defined in the HashArray struct. */
  uint16_t hashs[length]; /* array of hashs for each value, this helps with resolving bucket collision */
  uint8_t  values[length * value_size]; /* array holding all values. */
};
#define hasharray_get_hashs(array) (HashType *)(((uint8_t *)(array)) + sizeof(HashArray))
#define hasharray_get_values(array) ((uint8_t *)(array)) + sizeof(HashArray) + \
                                       ((array)->length * sizeof(HashType))
#define hasharray_get_value(array, idx) (hasharray_get_values(array) + ((idx) * (array)->value_size))

宏hasharray_get_hashs& hasharray_get_values用于获取'哈希'和' '值'数组。

我已经使用它来添加已经存储在数组中的复杂对象的快速查找。对象具有字符串'name'字段,用于查找。名称被哈希并插入到具有对象索引的hasharray中。存储在hasharray中的值可以是索引/指针/整个对象(我只使用小的16位索引值)。

如果要打包hasharray直到它几乎已满,那么你将需要使用完整的32位哈希值而不是上面定义的16位哈希值。当hasharray超过90%时,更大的32位散列将有助于保持搜索速度。

如上所定义的hasharray最多只能容纳65535,这很好,因为我从不在任何会有更多几百个值的东西上使用它。任何需要更多应该使用普通哈希表的东西。但是如果内存真的是个问题,那么HashSize类型可以改为32位。此外,我使用2的幂长度来保持哈希查找的快速。有些人更喜欢使用主存桶长度,但只有在散列函数分布不良时才需要这样做。

#define hasharray_empty_hash 0xFFFF /* hash value to mark empty slots. */
void *hasharray_search(HashArray *array, HashType hash, uint32_t *next) {
  HashType *hashs = hasharray_get_hashs(array);
  uint32_t mask = array->length - 1;
  uint32_t start_idx;
  uint32_t idx;

  hash = (hash == hasharray_empty_hash) ? 0 : hash; /* need one hash value to mark empty slots. */
  start_hash_idx = (hash & mask);
  if(*next == 0) {
    idx = start_idx; /* new search. */
  } else {
    idx = *next & mask; /* continuing search to next slot. */
  }

  /* find hash in hash array. */
  do {
    /* check for hash match. */
    if(hashs[idx] == hash) goto found_hash;
    /* check for end of chain. */
    if(hashs[idx] == hasharray_empty_hash) break;
    idx++;
    idx &= mask;
  } while(idx != start_idx);
  /* maximum tries reached (i.e. did a linear search of whole array) or end of chain. */
  return NULL;

found_hash:
  *next = idx + 1; /* where to continue search at, if this is not the right value. */
  return hasharray_get_values(array) + (idx * array->value_size);
}

将发生散列冲突,因此调用hasharray_search()的代码需要将搜索键与存储在值对象中的搜索键进行比较。如果它们不匹配,则再次调用hasharray_search()。此外,还可以存在非唯一键,因为搜索可以继续,直到返回“NULL”以查找与一个键匹配的所有值。搜索功能使用线性探测来自动缓存。

typedef struct {
  char *name;   /* this is the lookup key. */
  char *type;
  /* other field info... */
} Field;

typedef struct {
  Field *list;          /* array of Field objects. */
  HashArray *lookup;    /* hasharray for fast lookup of Field objects by name.  The values stored in this hasharray are 16bit indices. */
  uint32_t field_count; /* number of Field objects in 'list'. */
} Fields;

extern Fields *fields_new(uint16_t count) {
  Fields *fields;
  fields = calloc(1, sizeof(Fields));
  fields->list = calloc(count, sizeof(Field));
  /* allocate hasharray to hold at most 'count' uint16_t values.
   * The hasharray will round 'count' up to the next power-of-2.
   * That power-of-2 length must be atleast (count+1), so that there will always be one empty slot.
   */
  fields->lookup = hasharray_new(count, sizeof(uint16_t));
  fields->field_count = count;
}

extern Field *fields_lookup_by_name(Fields *fields, const char *name) {
  HashType hash = str_to_hash(name);
  Field *field;
  uint32_t next = 0;
  uint16_t *rc;
  uint16_t idx;

  do {
    rc = hasharray_search(fields->lookup, hash, &next);
    if(rc == NULL) break; /* field not found. */
    /* found a possible match. */
    idx = *rc;
    assert(idx < fields->field_count);
    field = &(fields->list[idx]);
    /* compare lookup name with field's name. */
    if(strcmp(name, field->name) == 0) {
      /* found match. */
      return field;
    }
    /* field didn't match continue search to next field. */
  } while(1);
  return NULL;
}

最坏情况搜索将降级为整个阵列的线性搜索,如果它已满99并且密钥不存在。如果键是整数,则线性搜索不应该是坏的,也只需要比较具有相同散列值的键。我尝试保持hasharrays的大小,使它们只有大约70-80%的满,如果值只有16位值,那么浪费在空槽上的空间并不多。使用这种设计,当使用16位散列时,每个空插槽只会浪费4个字节。 16位索引值。对象数组(上例中的字段结构)没有空白点。

我见过的大多数哈希表实现都没有存储计算出的哈希值,需要完全密钥比较来解决桶冲突。比较散列有很大帮助,因为只有一小部分散列值用于查找存储桶。

答案 1 :(得分:1)

回答这个问题:当负载系数接近1到确保最小内存浪费时,哪种hashmap冲突处理方案更好?

打开寻址/探测,允许高填充。因为正如你自己所说,冲突不需要额外的空间(只是,好吧,可能是时间 - 当然这也是假设哈希函数并不完美。)

如果您未在问题中指定“加载因子接近1”或包含“费用”指标,那么它将完全不同。

快乐的编码。

答案 2 :(得分:0)

正如其他人所说,在线性探测中,当载荷因子接近1时,时间复杂度接近线性搜索。 (当它充满时,它是无限的。)这里有一个记忆效率的交易。虽然隔离链总是给我们理论上恒定的时间。

通常,在线性探测下,建议将负载系数保持在1/8和1/2之间。当数组为1/2时,我们将其大小调整为原始数组的两倍。 (参考:算法。作者:Robert Sedgewick。凯文韦恩。)。删除时,我们也将数组的大小调整为原始大小的1/2。如果你真的很感兴趣,那么从我上面提到的那本书开始对你有好处。 实际上,据说0.72是我们通常使用的经验值。