上一章中,咱们实现了Hash表
中的插入
、搜索
和删除
接口,咱们在初始化hash表
时固定了大小为53
,为了方便扩展,本章将介绍如何修改hash表
的大小。函数
Hash表
大小如今,咱们的hash表
是固定大小(53
)的,当插入愈来愈多数据时,咱们的hash表
就会被插满,这个问题有两个缘由:性能
为了减小hash表
被插满的状况发生,当插入不少数据时,咱们能够增大hash表
的大小,hash表
中的count
属性表明已经插入的数据条数,在每次插入和删除时,咱们计算表的“负载”,或插入的数量和总的大小的比率,若是它高于或低于某些值,咱们会减少或扩大hash表
的大小。.net
咱们定义以下规则:code
负载>0.7
,就扩大
负载<0.1
,就缩小
要调整大小,咱们建立一个大约是当前大小的一半或两倍的新哈希表,并将全部未删除的项插入其中。blog
咱们的新hash表
大小应该是大约是当前大小的两倍或一半的素数,找到新的hash表
大小并不是易事。为了肯定hash表
的大小,咱们现设置一个最基本的大小,而后将实际大小定义为大于基本大小的第一个素数。扩大时,咱们先将基本大小加倍,找到第一个更大的素数,而后做为hash表
的大小,缩小时,咱们将大小减半并找到下一个更大的素数。接口
咱们先从基本大小50
开始,咱们使用最简单粗暴的方法经过检查每一个连续数是否为素数来查找下一个素数。这个简单粗暴的方法看起来不是很理想,可是咱们实际须要检查的值不多,而且花费的时间超过了从新散列表中每一个项目所花费的时间。get
首先,咱们先定义一个函数用来找到下一个素数,prime.h
和prime.c
的内容以下:hash
// prime.h int is_prime(const int x); int next_prime(int x);
// prime.c #include <math.h> #include "prime.h" /* * Return whether x is prime or not * * Returns: * 1 - prime * 0 - not prime * -1 - undefined (i.e. x < 2) */ int is_prime(const int x) { if (x < 2) { return -1; } if (x < 4) { return 1; } if ((x % 2) == 0) { return 0; } for (int i = 3; i <= floor(sqrt((double) x)); i += 2) { if ((x % i) == 0) { return 0; } } return 1; } /* * Return the next prime after x, or x if x is prime */ int next_prime(int x) { while (is_prime(x) != 1) { x++; } return x; }
下一步,咱们须要修改ht_new
函数,使之能够在建立hash表
时指定大小,为此咱们要建立一个新的函数ht_new_sized
,在ht_new
中咱们调用ht_new_sized
并给咱们的hash表
一个默认大小:it
// hash_table.c static ht_hash_table* ht_new_sized(const int base_size) { ht_hash_table* ht = xmalloc(sizeof(ht_hash_table)); ht->base_size = base_size; ht->size = next_prime(ht->base_size); ht->count = 0; ht->items = xcalloc((size_t)ht->size, sizeof(ht_item*)); return ht; } ht_hash_table* ht_new() { return ht_new_sized(HT_INITIAL_BASE_SIZE); }
如今一切准备就绪。在咱们的设置hash表
大小函数中,咱们须要检查以确保咱们没有将哈希表的大小减少到最小值如下,而后,咱们初始化一个所需大小的新hash表
,原表中全部非NULL
或者未被删除的都会插入到新hash表
中,而后咱们在删除旧的hash
表以前将属性赋值给新的hash表
。table
// hash_table.c static void ht_resize(ht_hash_table* ht, const int base_size) { if (base_size < HT_INITIAL_BASE_SIZE) { return; } ht_hash_table* new_ht = ht_new_sized(base_size); for (int i = 0; i < ht->size; i++) { ht_item* item = ht->items[I]; if (item != NULL && item != &HT_DELETED_ITEM) { ht_insert(new_ht, item->key, item->value); } } ht->base_size = new_ht->base_size; ht->count = new_ht->count; // To delete new_ht, we give it ht's size and items const int tmp_size = ht->size; ht->size = new_ht->size; new_ht->size = tmp_size; ht_item** tmp_items = ht->items; ht->items = new_ht->items; new_ht->items = tmp_items; ht_del_hash_table(new_ht); }
为了简化设置大小,咱们定义了两个函数:
// hash_table.c static void ht_resize_up(ht_hash_table* ht) { const int new_size = ht->base_size * 2; ht_resize(ht, new_size); } static void ht_resize_down(ht_hash_table* ht) { const int new_size = ht->base_size / 2; ht_resize(ht, new_size); }
要执行调整大小,咱们先检查插入和删除时hash表
上的负载。 若是它高于或低于0.7和0.1的预约义限制,咱们分别调高或调低。
为了不进行浮点运算,咱们将计数乘以100
,并检查它是高于仍是低于70
或10
:
// hash_table.c void ht_insert(ht_hash_table* ht, const char* key, const char* value) { const int load = ht->count * 100 / ht->size; if (load > 70) { ht_resize_up(ht); } // ... } void ht_delete(ht_hash_table* ht, const char* key) { const int load = ht->count * 100 / ht->size; if (load < 10) { ht_resize_down(ht); } // ... }
上一章:实现接口 下一章:附录:其余碰撞处理方法