二、開地址法
基本思想:當關鍵碼key的哈希地址H0 = hash(key)出現沖突時,以H0為基礎,產生另一個哈希地址H1 ,如果H1仍然沖突,再以H0
為基礎,產生另一個哈希地址H2 ,…,直到找出一個不沖突的哈希地址Hi ,將相應元素存入其中。這種方法有一個通用的再散列函
數形式: 
其中H0 為hash(key) ,m為表長,di稱為增量序列。增量序列的取值方式不同,相應的再散列方式也不同。主要有以下四種:
線性探測再散列
二次探測再散列
偽隨機探測再散列
雙散列法
(一)、線性探測再散列

假設給出一組表項,它們的關鍵碼為 Burke, Ekers, Broad, Blum, Attlee, Alton, Hecht, Ederly。采用的散列函數是:取其第一個字母在
字母表中的位置。
hash (x) = ord (x) - ord (‘A’)
這樣,可得
hash (Burke) = 1hash (Ekers) = 4
hash (Broad) = 1hash (Blum) = 1
hash (Attlee) = 0hash (Hecht) = 7
hash (Alton) = 0hash (Ederly) = 4
又設散列表為HT[26],m = 26。采用線性探查法處理溢出,則上述關鍵碼在散列表中散列位置如圖所示。紅色括號內的數字表示找
到空桶時的探測次數。比如輪到放置Blum 的時候,探測位置1,被占據,接着向下探測位置2還是不行,最后放置在位置3,總的探
測次數是3。

堆積現象
散列地址不同的結點爭奪同一個后繼散列地址的現象稱為堆積(Clustering),比如ALton 本來位置是0,直到探測了6次才找到合適位
置5。這將造成不是同義詞的結點也處在同一個探測序列中,從而增加了探測序列長度,即增加了查找時間。若散列函數不好、或裝
填因子a 過大,都會使堆積現象加劇。
下面給出具體的實現代碼,大體跟前面講過的鏈地址法差異不大,只是利用的結構不同,如下:

status 保存狀態,有EMPTY, DELETED, ACTIVE,刪除的時候只是邏輯刪除,即將狀態置為DELETED,當插入新的key 時,只要不
是ACTIVE 的位置都是可以放入,如果是DELETED位置,需要將原來元素先釋放free掉,再插入。
common.h:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#ifndef _COMMON_H_
#define _COMMON_H_ #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } \ while (0) #endif |
hash.h:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#ifndef _HASH_H_ #define _HASH_H_ typedef struct hash hash_t; typedef unsigned int (*hashfunc_t)(unsigned int, void *); hash_t *hash_alloc(unsigned int buckets, hashfunc_t hash_func); void hash_free(hash_t *hash); void *hash_lookup_entry(hash_t *hash, void *key, unsigned int key_size); void hash_add_entry(hash_t *hash, void *key, unsigned int key_size, void *value, unsigned int value_size); void hash_free_entry(hash_t *hash, void *key, unsigned int key_size); #endif /* _HASH_H_ */ |
hash.c:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
#include "hash.h"
#include "common.h" #include <assert.h> typedef enum entry_status { EMPTY, ACTIVE, DELETED } entry_status_t; typedef struct hash_node { enum entry_status status; void *key; void *value; } hash_node_t; struct hash { unsigned int buckets; hashfunc_t hash_func; hash_node_t *nodes; }; unsigned int hash_get_bucket(hash_t *hash, void *key); hash_node_t *hash_get_node_by_key(hash_t *hash, void *key, unsigned int key_size); hash_t *hash_alloc(unsigned int buckets, hashfunc_t hash_func) { hash_t *hash = (hash_t *)malloc(sizeof(hash_t)); //assert(hash != NULL); hash->buckets = buckets; hash->hash_func = hash_func; int size = buckets * sizeof(hash_node_t); hash->nodes = (hash_node_t *)malloc(size); memset(hash->nodes, 0, size); printf("The hash table has allocate.\n"); return hash; } void hash_free(hash_t *hash) { unsigned int buckets = hash->buckets; int i; for (i = 0; i < buckets; i++) { if (hash->nodes[i].status != EMPTY) { free(hash->nodes[i].key); free(hash->nodes[i].value); } } free(hash->nodes); free(hash);
printf(
"The hash table has free.\n"); } void *hash_lookup_entry(hash_t *hash, void *key, unsigned int key_size) { hash_node_t *node = hash_get_node_by_key(hash, key, key_size); if (node == NULL) { return NULL; } return node->value; } void hash_add_entry(hash_t *hash, void *key, unsigned int key_size, void *value, unsigned int value_size) { if (hash_lookup_entry(hash, key, key_size)) { fprintf(stderr, "duplicate hash key\n"); return; } unsigned int bucket = hash_get_bucket(hash, key); unsigned int i = bucket; // 找到的位置已經有人存活,向下探測 while (hash->nodes[i].status == ACTIVE) { i = (i + 1) % hash->buckets; if (i == bucket) { // 沒找到,並且表滿 return; } } hash->nodes[i].status = ACTIVE; if (hash->nodes[i].key) //釋放原來被邏輯刪除的項的內存 { free(hash->nodes[i].key); } hash->nodes[i].key = malloc(key_size); memcpy(hash->nodes[i].key, key, key_size); if (hash->nodes[i].value) //釋放原來被邏輯刪除的項的內存 { free(hash->nodes[i].value); } hash->nodes[i].value = malloc(value_size); memcpy(hash->nodes[i].value, value, value_size); } void hash_free_entry(hash_t *hash, void *key, unsigned int key_size) { hash_node_t *node = hash_get_node_by_key(hash, key, key_size); if (node == NULL) return; // 邏輯刪除,置標志位 node->status = DELETED; } unsigned int hash_get_bucket(hash_t *hash, void *key) { // 返回哈希地址 unsigned int bucket = hash->hash_func(hash->buckets, key); if (bucket >= hash->buckets) { fprintf(stderr, "bad bucket lookup\n"); exit(EXIT_FAILURE); } return bucket; } hash_node_t *hash_get_node_by_key(hash_t *hash, void *key, unsigned int key_size) { unsigned int bucket = hash_get_bucket(hash, key); unsigned int i = bucket; while (hash->nodes[i].status != EMPTY && memcmp(key, hash->nodes[i].key, key_size) != 0) { i = (i + 1) % hash->buckets; if (i == bucket) // 探測了一圈 { // 沒找到,並且表滿 return NULL; } } // 比對正確,還得確認是否還存活 if (hash->nodes[i].status == ACTIVE) { return &(hash->nodes[i]); } // 如果運行到這里,說明i為空位或已被刪除 return NULL; } |
main.c:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#include "hash.h" #include "common.h" typedef struct stu { char sno[5]; char name[32]; int age; } stu_t; typedef struct stu2 { int sno; char name[32]; int age; } stu2_t; unsigned int hash_str(unsigned int buckets, void *key) { char *sno = (char *)key; unsigned int index = 0; while (*sno) { index = *sno + 4 * index; sno++; } return index % buckets; } unsigned int hash_int(unsigned int buckets, void *key) { int *sno = (int *)key; return (*sno) % buckets; } int main(void) { stu2_t stu_arr[] = { { 1234, "AAAA", 20 }, { 4568, "BBBB", 23 }, { 6729, "AAAA", 19 } }; hash_t *hash = hash_alloc(256, hash_int); int size = sizeof(stu_arr) / sizeof(stu_arr[0]); int i; for (i = 0; i < size; i++) { hash_add_entry(hash, &(stu_arr[i].sno), sizeof(stu_arr[i].sno), &stu_arr[i], sizeof(stu_arr[i])); } int sno = 4568; stu2_t *s = (stu2_t *)hash_lookup_entry(hash, &sno, sizeof(sno)); if (s) { printf("%d %s %d\n", s->sno, s->name, s->age); } else { printf("not found\n"); } sno = 1234; hash_free_entry(hash, &sno, sizeof(sno)); s = (stu2_t *)hash_lookup_entry(hash, &sno, sizeof(sno)); if (s) { printf("%d %s %d\n", s->sno, s->name, s->age); } else { printf("not found\n"); } hash_free(hash); return 0; } |
simba@ubuntu:~/Documents/code/struct_algorithm/search/hash_table/linear_probing$ ./main
The hash table has allocate.
4568 BBBB 23
not found
The hash table has free.
與鏈地址法 示例還有一點不同,就是key 使用的是int 類型,所以必須再實現一個hash_int 哈希函數,根據key 產生哈希地址。
