日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

数据结构 -- 散列表

發(fā)布時(shí)間:2023/11/27 生活经验 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构 -- 散列表 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

散列表作為一種能夠提供高效插入,查找,刪除 以及遍歷的數(shù)據(jù)結(jié)構(gòu),被應(yīng)用在很多不同的存儲(chǔ)組件之中。
就像rocksdb中的hashskiplist,redis的有序集合,java的 LinkedHashMap 等 都是一些非常有特色的核心數(shù)據(jù)結(jié)構(gòu),來提供線上高效的數(shù)據(jù)操作能力。

本節(jié)對工業(yè)級散列表的基本實(shí)現(xiàn) 探索一番,希望加深自己對存儲(chǔ)產(chǎn)品設(shè)計(jì)理念的理解。

工業(yè)級的散列表需要具有如下能力:

  1. 初始大小
    散列表的初始大小,剛開始的時(shí)候需要擁有一定量的存儲(chǔ)空間,根據(jù)實(shí)際應(yīng)用情況可以通過設(shè)置散列表的初始大小,從而減少動(dòng)態(tài)擴(kuò)容的次數(shù),依次提升性能。

  2. 裝載因子 和 動(dòng)態(tài)擴(kuò)容
    最大裝載因子默認(rèn)是0.75, 即散列表中已存儲(chǔ)的元素個(gè)數(shù)達(dá)到了總大小的0.75,則開始擴(kuò)容

  3. 散列沖突的解決
    根據(jù)實(shí)際情況選擇通用的兩種方案: 開放尋址法 和 鏈表法
    開放尋址法:使用數(shù)組進(jìn)行底層存儲(chǔ),出現(xiàn)沖突時(shí)重新探測數(shù)據(jù)中的空閑位置,進(jìn)一步插入,該方法能夠利用CPU緩存功能進(jìn)行加速,但是比較耗費(fèi)內(nèi)存空間。
    基本過程如下:

    插入key3的時(shí)候hash函數(shù)計(jì)算的散列值也為3,和key2的散列值沖突,那么將向key2之后插入,但是發(fā)現(xiàn)key2之后沒有空間了,則跳到數(shù)據(jù)開頭重新遍歷找到第一個(gè)空閑的位置插入。

    鏈表法:將相同散列值的元素放入到相同的槽位,每一個(gè)槽位用鏈表管理相同hash值的元素
    該方法能夠高效利用內(nèi)存(鏈表節(jié)點(diǎn)生成新節(jié)點(diǎn)的時(shí)候才會(huì)分配空間),只是對CPU緩存不太友好,地址之間并不是連續(xù)的,CPU緩存基本不能生效。(這里可以通過一些有序的數(shù)據(jù)結(jié)構(gòu)進(jìn)行優(yōu)化-- 跳表和紅黑樹)

    插入的key3有和key1相同的散列值,則將key3直接插入到key1對應(yīng)的bucket鏈表末尾,實(shí)際過程需要有序,所以這里插入到hashtab[2]的時(shí)候還需要找到對應(yīng)的鏈表節(jié)點(diǎn)前驅(qū)。

  4. 散列函數(shù)
    散列函數(shù)的設(shè)計(jì)不追求復(fù)雜,但是需要高效,計(jì)算但散列值要分布均勻。
    java的LinkedHashMap的散列函數(shù)設(shè)計(jì)如下:

    int hash(Object key) {int h = key.hashCode();return (h ^ (h >>> 16)) & (capitity -1); //capicity表示散列表的大小
    }
    

    其中,hashCode()返回的是Java對象的hash code。比如String類型的對象的hashCode()就是下面這樣:

    public int hashCode() {int var1 = this.hash;if(var1 == 0 && this.value.length > 0) {char[] var2 = this.value;for(int var3 = 0; var3 < this.value.length; ++var3) {var1 = 31 * var1 + var2[var3];}this.hash = var1;}return var1;
    }
    

    設(shè)計(jì)的過程中盡可能保證數(shù)據(jù)的隨機(jī)性,就像手機(jī)號(hào)的后四位 一般是隨機(jī)均勻分布,這樣取用數(shù)據(jù)的過程即可作為hash函數(shù)。

通過以上四點(diǎn)的設(shè)計(jì),我們基本能夠完成一個(gè)工業(yè)級的散列表實(shí)現(xiàn),再做一個(gè)總結(jié),工業(yè)級的散列表的特性:

  • 支持快速的查詢、插入、刪除操作;
  • 內(nèi)存占用合理,不能浪費(fèi)過多的內(nèi)存空間;
  • 性能穩(wěn)定,極端情況下,散列表的性能也不會(huì)退化到無法接受的情況

工業(yè)級散列表的設(shè)計(jì)實(shí)現(xiàn)思路:

  • 設(shè)計(jì)一個(gè)合適的散列函數(shù);
  • 定義裝載因子閾值,并且設(shè)計(jì)動(dòng)態(tài)擴(kuò)容策略
  • 選擇合適的散列沖突解決方法

通過以上設(shè)計(jì),使用C語言編寫一個(gè)簡單的工業(yè)級散列表實(shí)現(xiàn)如下,散列沖突是通過鏈表解決的

listhash.h

#ifndef __HASHTAB_H__
#define __HASHTAB_H__typedef struct _hashtab_node
{void * key;void * data;struct _hashtab_node *next;
}hashtab_node;typedef struct _hashtab
{hashtab_node **htables; /*哈希桶*/int size;              /*哈希桶的最大數(shù)量*/int nel;               /*哈希桶中元素的個(gè)數(shù)*/int (*hash_value)(struct _hashtab *h,const void *key); /*哈希函數(shù)*/int (*keycmp)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比較函數(shù),當(dāng)哈希數(shù)值一致時(shí)使用*/void (*hash_node_free)(hashtab_node *node);
}hashtab;#define HASHTAB_MAX_NODES  (0xffffffff)typedef int (*hash_key_func)(struct _hashtab *h,const void *key); /*哈希函數(shù)*/
typedef int (*keycmp_func)(struct _hashtab*h,const void *key1,const void *key2);/*哈希key比較函數(shù),當(dāng)哈希數(shù)值一致時(shí)使用*/
typedef void (*hash_node_free_func)(hashtab_node *node);
/*根據(jù)當(dāng)前結(jié)構(gòu)體元素的地址,獲取到結(jié)構(gòu)體首地址*/
#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container(ptr,type,member) ({\const typeof( ((type *)0)->member) *__mptr = (ptr);\(type *) ( (char *)__mptr - offsetof(type,member));})hashtab * hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free);
void *hashtab_expand(hashtab*h);
void hashtab_destory(hashtab *h);
int hashtab_insert(hashtab * h,void *key,void *data);
hashtab_node *hashtab_delete(hashtab *h, void *key);
void *hashtab_search(hashtab*h,void *key);#endif

listhash.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include"listhash.h"/* 創(chuàng)建 */
hashtab *hashtab_create(int size,hash_key_func hash_value,keycmp_func keycmp,hash_node_free_func hash_node_free) 
{hashtab *h = NULL;int hash_num = 0; // 初始化hash元素的個(gè)數(shù)if(size < 0 || hash_value == NULL || keycmp == NULL) {return NULL;}h = (hashtab *)malloc(sizeof(hashtab));if(h == NULL) {return NULL;}h->htables = (hashtab_node**)malloc(size * sizeof(hashtab_node*));if(h->htables == NULL) {return NULL;}h->size = size;h->hash_value = hash_value;h->keycmp = keycmp;h->hash_node_free = hash_node_free;h->nel = 0;for(;hash_num < size; hash_num++) {h->htables[hash_num] = NULL;}return h;
}/* 銷毀 */
void hashtab_destory(hashtab *h) 
{int i = 0;hashtab_node *cur = NULL;hashtab_node *tmp = NULL;if(h == NULL) {return;}for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {tmp = cur;cur = cur->next;h->hash_node_free(tmp);}}free(h->htables);free(h);return ;
}/* 插入 */
int hashtab_insert(hashtab *h ,void *key, void *data) {if(h == NULL || key == NULL || data == NULL) {return -1;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hashtab_node *tmp = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];/* hashtable 中的元素在hash鏈上也是有序的 */while(cur != NULL && (h->keycmp(h,key,cur->key) > 0)) {// 找到待插入key的前驅(qū)節(jié)點(diǎn)prev = cur;cur = cur->next;}if(cur != NULL && (h->keycmp(h, key, cur->key) == 0)) { // 當(dāng)前key存在return 1;}tmp = (hashtab_node *)malloc(sizeof(hashtab_node));if(tmp == NULL) {return -1;}tmp->key = key;tmp->data = data;if(prev == NULL) {tmp->next = h->htables[hvalue];h->htables[hvalue] = tmp;}else {tmp->next = prev->next;prev->next = tmp;}h->nel ++;// if(h->size * 3 / 4 <= h->nel) {//    hashtab_expand(h);// }return 0;
}/* 刪除 */
hashtab_node *hashtab_delete(hashtab *h, void *key) 
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hashtab_node *prev = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];while(cur != NULL && (h->keycmp(h,key,cur->key) >= 0) ) {// 找到待刪除節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)if(h->keycmp(h,key,cur->key) == 0) { // 找到匹配的keyif(prev == NULL) { // 需要?jiǎng)h除的key就是hvalue所在hash鏈上的第一個(gè)keyh->htables[hvalue] = cur -> next;}else {prev->next = cur->next;}h->nel --;return cur;}prev = cur;cur = cur ->next;}return NULL;
}/* 查找 */
void *hashtab_search(hashtab*h,void *key) 
{if(h == NULL || key == NULL) {return NULL;}unsigned int hvalue = 0;hashtab_node *cur = NULL;hvalue = h->hash_value(h,key);cur = h->htables[hvalue];if(cur == NULL) { // 先確認(rèn)hash桶是否存在return NULL;}while(cur != NULL) {if(h->keycmp(h,key,cur->key) == 0) {return cur->data;}cur = cur ->next;}return NULL;
}void hashtab_dump(hashtab *h) 
{int i = 0;hashtab_node *cur = NULL;if(h == NULL) {return;}printf("\r\n----開始--size[%d],nel[%d]------------", h->size, h->nel);for(;i< h->size; ++i) {printf("\r\n htables[%d]:",i);cur = h->htables[i];while(cur != NULL) {printf("key[%s],data[%s] ", cur->key, cur->data);cur = cur ->next;}}printf("\r\n----結(jié)束--size[%d],nel[%d]------------", h->size, h->nel);
}/* 擴(kuò)容 */
void  *hashtab_expand(hashtab *tmp_h) {if(tmp_h == NULL || (tmp_h ->size * 3 / 4 > tmp_h->nel)) {return NULL;}printf("begin expand\n");hashtab *new_h = NULL;hashtab *h = tmp_h;hashtab_node *cur = NULL;int i = 0;new_h = hashtab_create(h->size * 2, h->hash_value,h->keycmp, h->hash_node_free);for (;i < h->size; ++i) {cur = h->htables[i];while(cur != NULL) {hashtab_insert(new_h, cur->key, cur->data);cur = cur->next;}}printf("before destory\n");hashtab_destory(tmp_h);printf("end destory\n");// hashtab_dump(new_h);tmp_h = new_h;return NULL;
}struct test_node
{/* data */char key[30];char data[30];
};unsigned int simple_hash(const char *str)
{register unsigned int hash = 0;register unsigned int seed = 131;while(*str){hash = hash*seed + *str++;}return hash & (0x7FFFFFFF);
}int hashtable_hvalue(hashtab *h, const void *key) 
{return simple_hash(key) % h->size;
}int hashtable_compare(hashtab*h, const void *key1, const void *key2)
{return strcmp(key1, key2); 
}void hashtable_node_free(hashtab_node *cur) 
{struct test_node *tmp = NULL;tmp = container(cur->key,struct test_node,key);free(tmp);free(cur);
}int main ()
{int res = 0;char *pres = NULL;hashtab_node * node = NULL;struct test_node *p = NULL;hashtab *h = NULL;h = hashtab_create(6,hashtable_hvalue,hashtable_compare,hashtable_node_free);// 創(chuàng)建一個(gè)hash桶大小為5的hash表assert(h!= NULL);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 輸入key  value,輸入\"quit\"退出");scanf("%s",p->key);scanf("%s",p->data);if(strcmp(p->key,"quit") == 0){free(p);break;}res = hashtab_insert(h,p->key,p->data);if (res != 0){free(p);printf("\r\n key[%s],data[%s] insert failed %d",p->key,p->data,res);}else{printf("\r\n key[%s],data[%s] insert success %d",p->key,p->data,res);}}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 請輸入key 查詢value的數(shù)值,當(dāng)可以等于\"quit\"時(shí)退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}pres = hashtab_search(h,p->key);if (pres == NULL){printf("\r\n key[%s] search data failed",p->key);}else{printf("\r\n key[%s],search data[%s] success",p->key,pres);}free(p);}hashtab_dump(h);while(1){p = (struct test_node*)malloc(sizeof(struct test_node));assert(p != NULL);printf("\r\n 請輸入key 刪除節(jié)點(diǎn)的數(shù)值,當(dāng)可以等于\"quit\"時(shí)退出");scanf("%s",p->key);if(strcmp(p->key,"quit") == 0){free(p);break;}node = hashtab_delete(h,p->key);if (node == NULL){printf("\r\n key[%s] delete node failed ",p->key);}else{printf("\r\n key[%s],delete data[%s] success",node->key,node->data);h->hash_node_free(node);}free(p);hashtab_dump(h);}hashtab_destory(h);return 0;}

總結(jié)

以上是生活随笔為你收集整理的数据结构 -- 散列表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。