C語言實現一個簡易的Hash table(6)

上一章中,咱們實現了Hash表中的插入搜索刪除接口,咱們在初始化hash表時固定了大小爲53,爲了方便擴展,本章將介紹如何修改hash表的大小。segmentfault

設置Hash表大小

如今,咱們的hash表是固定大小(53)的,當插入愈來愈多數據時,咱們的hash表就會被插滿,這個問題有兩個緣由:函數

  1. 哈希表的性能隨着高衝突率而下降
  2. 咱們的'hash表'只能存儲固定數量的記錄,若是咱們存儲更多,將沒法插入數據

爲了減小hash表被插滿的狀況發生,當插入不少數據時,咱們能夠增大hash表的大小,hash表中的count屬性表明已經插入的數據條數,在每次插入和刪除時,咱們計算表的「負載」,或插入的數量和總的大小的比率,若是它高於或低於某些值,咱們會減少或擴大hash表的大小。性能

咱們定義以下規則:spa

  1. 若是負載>0.7,就擴大
  2. 若是負載<0.1,就縮小

要調整大小,咱們建立一個大約是當前大小的一半或兩倍的新哈希表,並將全部未刪除的項插入其中。code

咱們的新hash表大小應該是大約是當前大小的兩倍或一半的素數,找到新的hash表大小並不是易事。爲了肯定hash表的大小,咱們現設置一個最基本的大小,而後將實際大小定義爲大於基本大小的第一個素數。擴大時,咱們先將基本大小加倍,找到第一個更大的素數,而後做爲hash表的大小,縮小時,咱們將大小減半並找到下一個更大的素數。接口

咱們先從基本大小50開始,咱們使用最簡單粗暴的方法經過檢查每一個連續數是否爲素數來查找下一個素數。這個簡單粗暴的方法看起來不是很理想,可是咱們實際須要檢查的值不多,而且花費的時間超過了從新散列表中每一個項目所花費的時間。get

首先,咱們先定義一個函數用來找到下一個素數,prime.hprime.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,並檢查它是高於仍是低於7010

// 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);
    }
    // ...
}

上一章:實現接口
下一章:附錄:其餘碰撞處理的方法

相關文章
相關標籤/搜索