上一章中,咱們實現了Hash表
中的插入
、搜索
和刪除
接口,咱們在初始化hash表
時固定了大小爲53
,爲了方便擴展,本章將介紹如何修改hash表
的大小。segmentfault
Hash表
大小如今,咱們的hash表
是固定大小(53
)的,當插入愈來愈多數據時,咱們的hash表
就會被插滿,這個問題有兩個緣由:函數
爲了減小hash表
被插滿的狀況發生,當插入不少數據時,咱們能夠增大hash表
的大小,hash表
中的count
屬性表明已經插入的數據條數,在每次插入和刪除時,咱們計算表的「負載」,或插入的數量和總的大小的比率,若是它高於或低於某些值,咱們會減少或擴大hash表
的大小。性能
咱們定義以下規則:spa
負載>0.7
,就擴大
負載<0.1
,就縮小
要調整大小,咱們建立一個大約是當前大小的一半或兩倍的新哈希表,並將全部未刪除的項插入其中。code
咱們的新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); } // ... }
上一章:實現接口
下一章:附錄:其餘碰撞處理的方法