詳解散列表算法與其相關(guān)的C語言實(shí)現(xiàn)
散列表(也叫哈希表)是一種查找算法,與鏈表、樹等算法不同的是,散列表算法在查找時(shí)不需要進(jìn)行一系列和關(guān)鍵字(關(guān)鍵字是數(shù)據(jù)元素中某個(gè)數(shù)據(jù)項(xiàng)的值,用以標(biāo)識(shí)一個(gè)數(shù)據(jù)元素)的比較操作。
散列表算法希望能盡量做到不經(jīng)過任何比較,通過一次存取就能得到所查找的數(shù)據(jù)元素,因而必須要在數(shù)據(jù)元素的存儲(chǔ)位置和它的關(guān)鍵字(可用key表示)之間建立一個(gè)確定的對(duì)應(yīng)關(guān)系,使每個(gè)關(guān)鍵字和散列表中一個(gè)唯一的存儲(chǔ)位置相對(duì)應(yīng)。因此在查找時(shí),只要根據(jù)這個(gè)對(duì)應(yīng)關(guān)系找到給定關(guān)鍵字在散列表中的位置即可。這種對(duì)應(yīng)關(guān)系被稱為散列函數(shù)(可用h(key)表示)。
根據(jù)設(shè)定的散列函數(shù)h(key)和處理沖突的方法將一組關(guān)鍵字key映像到一個(gè)有限的連續(xù)的地址區(qū)間上,并以關(guān)鍵字在地址區(qū)間中的像作為數(shù)據(jù)元素在表中的存儲(chǔ)位置,這種表便被稱為散列表,這一映像過程稱為散列,所得存儲(chǔ)位置稱為散列地址。
關(guān)鍵字、散列函數(shù)以及散列表的關(guān)系如下圖所示:
1、散列函數(shù)
散列函數(shù)是從關(guān)鍵字到地址區(qū)間的映像。
好的散列函數(shù)能夠使得關(guān)鍵字經(jīng)過散列后得到一個(gè)隨機(jī)的地址,以便使一組關(guān)鍵字的散列地址均勻地分布在整個(gè)地址區(qū)間中,從而減少?zèng)_突。
常用的構(gòu)造散列函數(shù)的方法有:
(1)、直接定址法
取關(guān)鍵字或關(guān)鍵字的某個(gè)線性函數(shù)值為散列地址,即:
h(key) = key 或 h(key) = a * key + b
其中a和b為常數(shù)。
(2)、數(shù)字分析法
(3)、平方取值法
取關(guān)鍵字平方后的中間幾位為散列地址。
(4)、折疊法
將關(guān)鍵字分割成位數(shù)相同的幾部分(最后一部分的位數(shù)可以不同),然后取這幾部分的疊加和(舍去進(jìn)位)作為散列地址。
(5)、除留余數(shù)法
取關(guān)鍵字被某個(gè)不大于散列表表長(zhǎng)m的數(shù)p除后所得的余數(shù)為散列地址,即:
h(key) = key MOD p p ≤ m
(6)、隨機(jī)數(shù)法
選擇一個(gè)隨機(jī)函數(shù),取關(guān)鍵字的隨機(jī)函數(shù)值為它的散列地址,即:
h(key) = random(key)
其中random為隨機(jī)函數(shù)。
2、處理沖突
對(duì)不同的關(guān)鍵字可能得到同一散列地址,即key1 ≠ key2,而h(key1)= h(key2),這種現(xiàn)象稱為沖突。具有相同函數(shù)值的關(guān)鍵字對(duì)該散列函數(shù)來說稱作同義詞。
在一般情況下,散列函數(shù)是一個(gè)壓縮映像,這就不可避免地會(huì)產(chǎn)生沖突,因此,在創(chuàng)建散列表時(shí)不僅要設(shè)定一個(gè)好的散列函數(shù),而且還要設(shè)定一種處理沖突的方法。
常用的處理沖突的方法有:
(1)、開放定址法
hi =(h(key) + di) MOD m i =1,2,…,k(k ≤ m-1)
其中,h(key)為散列函數(shù),m為散列表表長(zhǎng),di為增量序列,可有下列三種取法:
1)、di = 1,2,3,…,m-1,稱線性探測(cè)再散列;
2)、di = 12,-12,22,-22,32,…,±k2 (k ≤m/2),稱二次探測(cè)再散列;
3)、di = 偽隨機(jī)數(shù)序列,稱偽隨機(jī)探測(cè)再散列。
(2)、再散列法
hi = rhi(key) i = 1,2,…,k
rhi均是不同的散列函數(shù)。
(3)、鏈地址法
將所有關(guān)鍵字為同義詞的數(shù)據(jù)元素存儲(chǔ)在同一線性鏈表中。假設(shè)某散列函數(shù)產(chǎn)生的散列地址在區(qū)間[0,m-1]上,則設(shè)立一個(gè)指針型向量void *vec[m],其每個(gè)分量的初始狀態(tài)都是空指針。凡散列地址為i的數(shù)據(jù)元素都插入到頭指針為vec[i]的鏈表中。在鏈表中的插入位置可以在表頭或表尾,也可以在表的中間,以保持同義詞在同一線性鏈表中按關(guān)鍵字有序排列。
(4)、建立一個(gè)公共溢出區(qū)
相關(guān)的C語言解釋
hash.h
哈希表數(shù)據(jù)結(jié)構(gòu)&&接口定義頭文件
#ifndef HASH_H #define HASH_H #define HASH_TABLE_INIT_SIZE 7 #define SUCCESS 1 #define FAILED 0 /** * 哈希表槽的數(shù)據(jù)結(jié)構(gòu) */ typedef struct Bucket { char *key; void *value; struct Bucket *next; } Bucket; /** * 哈希表數(shù)據(jù)結(jié)構(gòu) */ typedef struct HashTable { int size; // 哈希表大小 int elem_num; // 哈希表已經(jīng)保存的數(shù)據(jù)元素個(gè)數(shù) Bucket **buckets; } HashTable; int hashIndex(HashTable *ht, char *key); int hashInit(HashTable *ht); int hashLookup(HashTable *ht, char *key, void **result); int hashInsert(HashTable *ht, char *key, void *value); int hashRemove(HashTable *ht, char *key); int hashDestory(HashTable *ht); #endif
hash.c
哈希表操作函數(shù)具體實(shí)現(xiàn)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "hash.h" /** * 初始化哈希表 * * T = O(1) * */ int hashInit(HashTable *ht) { ht->size = HASH_TABLE_INIT_SIZE; ht->elem_num = 0; ht->buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *)); if (ht->buckets == NULL) return FAILED; else return SUCCESS; } /** * 散列函數(shù) * * T = O(n) * */ int hashIndex(HashTable *ht, char *key) { int hash = 0; while (*key != '\0') { hash += (int)*key; key ++; } return hash % ht->size; } /** * 哈希查找函數(shù) * * T = O(n) * */ int hashLookup(HashTable *ht, char *key, void **result) { int index = hashIndex(ht, key); Bucket *bucket = ht->buckets[index]; while (bucket) { if (strcmp(bucket->key, key) == 0) { *result = bucket->value; return SUCCESS; } bucket = bucket->next; } return FAILED; } /** * 哈希表插入操作 * * T = O(1) * */ int hashInsert(HashTable *ht, char *key, void *value) { int index = hashIndex(ht, key); Bucket *org_bucket, *tmp_bucket; org_bucket = tmp_bucket = ht->buckets[index]; // 檢查key是否已經(jīng)存在于hash表中 while (tmp_bucket) { if (strcmp(tmp_bucket->key, key) == 0) { tmp_bucket->value = value; return SUCCESS; } tmp_bucket = tmp_bucket->next; } Bucket *new = (Bucket *)malloc(sizeof(Bucket)); if (new == NULL) return FAILED; new->key = key; new->value = value; new->next = NULL; ht->elem_num += 1; // 頭插法 if (org_bucket) { new->next = org_bucket; } ht->buckets[index] = new; return SUCCESS; } /** * 哈希刪除函數(shù) * * T = O(n) * */ int hashRemove(HashTable *ht, char *key) { int index = hashIndex(ht, key); Bucket *pre, *cur, *post; pre = NULL; cur = ht->buckets[index]; while (cur) { if (strcmp(cur->key, key) == 0) { post = cur->next; if (pre == NULL) { ht->buckets[index] = post; } else { pre->next = post; } free(cur); return SUCCESS; } pre = cur; cur = cur->next; } return FAILED; } /** * 哈希表銷毀函數(shù) * * T = O(n) */ int hashDestory(HashTable *ht) { int i; Bucket *cur, *tmp; cur = tmp = NULL; for (i = 0; i < ht->size; i ++) { cur = ht->buckets[i]; while (cur) { tmp = cur->next; free(cur); cur = tmp; } } free(ht->buckets); return SUCCESS; }
test.c
單元測(cè)試文件
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "hash.h" int main(int argc, char **argv) { HashTable *ht = (HashTable *)malloc(sizeof(HashTable)); int result = hashInit(ht); assert(result == SUCCESS); /* Data */ int int1 = 10; int int2 = 20; char str1[] = "Hello World!"; char str2[] = "Value"; char str3[] = "Hello New World!"; /* to find data container */ int *j = NULL; char *find_str = NULL; /* Test Key Insert */ printf("Key Insert:\n"); hashInsert(ht, "FirInt", &int1); hashInsert(ht, "FirStr", str1); hashInsert(ht, "SecStr", str2); printf("Pass Insert\n"); /* Test Key Lookup*/ printf("Key Lookup:\n"); result = hashLookup(ht, "FirStr", &find_str); assert(result == SUCCESS); printf("pass lookup, the value is %s\n", find_str); /* Test Update */ printf("Key Update:\n"); hashInsert(ht, "FirStr", str3); result = hashLookup(ht, "FirStr", &find_str); assert(result == SUCCESS); printf("pass update, the value is %s\n", find_str); return 0; }
編譯方法
gcc -Wall -g -o main test.c hash.c
運(yùn)行結(jié)果
開放尋址法
在開放尋址法(open addressing)中,所有的元素都存放在散列表里。亦即,每個(gè)表項(xiàng)或包含動(dòng)態(tài)集合的一個(gè)元素,或包含NIL。當(dāng)查找一個(gè)元素時(shí),要檢查所有的表項(xiàng),直到找到所需的元素,或者最終發(fā)現(xiàn)該元素不在表中。不像在鏈接法中,這沒有鏈表,也沒有元素存放在散列表外。在這種方法中,散列表可能會(huì)被填滿,以致于不能插入任何新的元素,但裝載因子a是絕對(duì)不會(huì)超過1的
線性探測(cè)法
第一次沖突移動(dòng)1個(gè)單位,再次沖突時(shí),移動(dòng)2個(gè),再次沖突,移動(dòng)3個(gè)單位,依此類推
它的散列函數(shù)是:H(x) = (Hash(x) + F(i)) mod TableSize, 且F(0) = 0
舉例(騰訊面試題目)
已知一個(gè)線性表(38, 25, 74, 63, 52, 48),假定采用散列函數(shù) h(key) = key % 7 計(jì)算散列地址,并散列存儲(chǔ)在散列表 A[0..6]中,若采用線性探測(cè)方法解決沖突,則在該散列表上進(jìn)行等概率成功查找的平均長(zhǎng)度為 ?
下邊模擬線性探測(cè):
38 % 7 == 3, 無沖突, ok
25 % 7 == 4, 無沖突, ok
74 % 7 == 4, 沖突, (4 + 1)% 7 == 5, 無沖突,ok
63 % 7 == 0, 無沖突, ok
52 % 7 == 3, 沖突, (3 + 1) % 7 == 4. 沖突, (4 + 1) % 7 == 5, 沖突, (5 + 1)%7 == 6,無沖突,ok
48 % 7 == 6, 沖突, (6 + 1) % 7 == 0, 沖突, (0 + 1) % 7 == 1,無沖突,ok
畫圖如下:
平均查找長(zhǎng)度 = (1 + 3 + 1 + 1 + 2 + 3) % 6 = 2
線性探測(cè)方法比較容易實(shí)現(xiàn),但它卻存在一個(gè)問題,稱為一次群集(primary clustering).隨著時(shí)間的推移,連續(xù)被占用的槽不斷增加,平均查找時(shí)間也隨著不斷增加。集群現(xiàn)象很容易出現(xiàn),這是因?yàn)楫?dāng)一個(gè)空槽前有i個(gè)滿的槽時(shí),該空槽為下一個(gè)將被占用的槽的概率是 (i + 1) / n.連續(xù)占用的槽的序列會(huì)變得越來越長(zhǎng),因而平均查找時(shí)間也會(huì)隨之增加
平方探測(cè)
為了避免上面提到的一個(gè)群集的問題:第一次沖突時(shí)移動(dòng)1(1的平方)個(gè)單位,再次沖突時(shí),移動(dòng)4(2的平方)個(gè)單位,還沖突,移動(dòng)9個(gè)單位,依此類推。F(i) = i * i
欄 目:C語言
本文標(biāo)題:詳解散列表算法與其相關(guān)的C語言實(shí)現(xiàn)
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2900.html
您可能感興趣的文章
- 01-10求子數(shù)組最大和的解決方法詳解
- 01-10深入二叉樹兩個(gè)結(jié)點(diǎn)的最低共同父結(jié)點(diǎn)的詳解
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)- 解析最少換車次數(shù)的問題詳解
- 01-10數(shù)據(jù)結(jié)構(gòu)課程設(shè)計(jì)-用棧實(shí)現(xiàn)表達(dá)式求值的方法詳解
- 01-10HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用分析詳解
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10如何查看進(jìn)程實(shí)際的內(nèi)存占用情況詳解
- 01-10深入Main函數(shù)中的參數(shù)argc,argv的使用詳解
- 01-10APUE筆記之:進(jìn)程環(huán)境詳解
- 01-10深入第K大數(shù)問題以及算法概要的詳解


閱讀排行
- 1C語言 while語句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對(duì)數(shù)函數(shù)的表達(dá)式 c語言中對(duì)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10delphi制作wav文件的方法
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 04-02jquery與jsp,用jquery