散列表

  散列表Hash table,也叫哈希表),是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。类似于数学中的映射关系。

  现假设关键字的全域为U = {k | 0 <= k < n}

1、直接寻址表

  如果全域U比较小,即n的数值比较小,且没有重复元素,那么可以用数组T[0, m-1](m = n)做为直接寻址表,每个槽位T[k] = tk,tk = NIL表示不存在关键字为k的元素,否则表示有关键字为k的元素。

SEARCH(T, x)
    return T[x]

INSERT(T, x)
    T[x] = x

DELETE(T, x)
    T[x] = NIL

直接寻址法限制较多,不常用。

 2、散列表

当全域U很大时,可以用函数h把U中元素k映射到表T[1, m]中的某个位置,即:

h: U{k} --> T[i], (k IN [0, n-1], i IN [0, m-1])

这样我们处理的值就从n降到了m。这里的函数h称为散列函数。

好的散列函数应该满足如下性质:每个关键字都等可能的映射到T[0, m-1]中的m个槽位中,且与其他关键字散列到哪个槽位无关

但这个性质很难满足. 散列函数假定关键字为自然数N的子集,如果关键字不是自然数,则将其变换为自然数。

一般有如下几种散列函数:

2.1、除法散列

h(k) = k mod m  // m表示槽位数,k表示关键字

一般讲m选择为与2的整数幂不太接近的质数。

2.2、乘法散列

h(k) = floor(m(kA mod 1)) // 0 < A < 1

其中 kA mod 1 表示取 kA的小数部分,floor表示向下取整。

A可以取黄金分割数 (sqrt(5) - 1) / 2

2.3 解决冲突

从全域U --> 表T, 可能存在k1和k2同时映射到T[i]的情况,发生了冲突,可以通过链接法来解决。

链接法实际是使用数组T[0, m-1], 数组的任意元素T[i]为一个链表的表头来实现的。

3、散列表简单实现

采用除法散列,并用双链表解决散列冲突的简单散列表如下:

  1 static const int hash_size = 331;
  2 #define hash(x) ((unsigned int)x % hash_size)
  3 
  4 struct listNode
  5 {
  6     int key;
  7     int value;
  8     struct listNode *next;
  9     struct listNode *prev;
 10     listNode(int x, int y): key(x), value(y), next(NULL), prev(NULL) {}
 11     listNode(): key(0), value(0), next(NULL), prev(NULL) {}
 12 };
 13 
 14 class hash_table
 15 {
 16 public:
 17     hash_table()
 18     {
 19         for (int i = 0; i < hash_size; i++)
 20         {
 21             hash_map[i].next = &hash_map[i]; 
 22             hash_map[i].prev = &hash_map[i]; 
 23         }
 24         elem_size = 0;
 25     }
 26     struct listNode *find(int key);
 27     void insert(int key, int value);
 28     void erase(int key);
 29     bool empty();
 30     int size();
 31 private:
 32     struct listNode hash_map[hash_size];
 33     int elem_size;
 34 };
 35 
 36 void __list_add(struct listNode *prev, struct listNode *next, struct listNode *node)
 37 {
 38     prev->next = node;
 39     node->next = next;
 40     next->prev = node;
 41     node->prev = prev;
 42 }
 43 
 44 void list_add(struct listNode *head, struct listNode *node)
 45 {
 46     __list_add(head, head->next, node);
 47 }
 48 
 49 void __list_del(struct listNode *prev, struct listNode *next)
 50 {
 51     prev->next = next;
 52     next->prev = prev;
 53 }
 54 
 55 void list_del(struct listNode *node)
 56 {
 57     __list_del(node->prev, node->next);
 58 }
 59 
 60 bool list_empty(struct listNode *node)
 61 {
 62     return (node->next == node);
 63 }
 64 
 65 bool list_tail(struct listNode *head, struct listNode *node)
 66 {
 67     return (node->next == head);
 68 }
 69 
 70 bool hash_table::empty()
 71 {
 72     return (elem_size == 0);
 73 }
 74 
 75 int hash_table::size()
 76 {
 77     return elem_size;
 78 }
 79 
 80 struct listNode *hash_table::find(int key)
 81 {
 82     struct listNode *head = &hash_map[hash(key)], *visit = head;
 83     while (!list_tail(head, visit))
 84     {
 85         visit = visit->next;
 86         if (visit->key == key)
 87             return visit;
 88     }
 89     return NULL;
 90 }
 91 
 92 void hash_table::insert(int key, int value)
 93 {
 94     struct listNode *visit = NULL;
 95     
 96     visit = find(key);
 97     if (visit != NULL)
 98     {
 99         visit->value = value;
100     }
101     else
102     {
103         visit = new listNode(key, value);
104         list_add(&hash_map[hash(key)], visit);
105         elem_size++;
106     }
107 }
108 
109 void hash_table::erase(int key)
110 {
111     struct listNode *visit;
112 
113     visit = find(key);
114     if (visit != NULL)
115     {
116         list_del(visit);
117         delete visit;
118         elem_size--;
119     }
120 }

为了简单起见,没有实现rehash方法, 仅仅作为一个散列表的简单实现.

原文地址:https://www.cnblogs.com/ym65536/p/4132883.html