簡單的hash就是用數組加鏈表的組合來實現,這種hash很簡單,但hash的思想在那。html
#ifndef _HASH_H_ #define _HASH_H_ typedef struct _ListNode { struct _ListNode *prev; struct _ListNode *next; void *data; }ListNode; typedef ListNode *List; typedef ListNode *Position; typedef struct _HashTbl { int TableSize; List *Thelists; }HashTbl; int Hash( void *key, int TableSize ); HashTbl *InitHash( int TableSize ); void Insert( void *key, HashTbl *HashTable ); Position Find( void *key, HashTbl *HashTable ); void Destory( HashTbl *HashTable ); void *Retrieve( Position P ); #endif
#include <stdio.h> #include <stdlib.h> #include "hash.h" int Hash( void *key, int TableSize ) { char c; int i; int hval = 0; for( i = 1;(c = *(char *)key++) != 0; i++) hval += c*i; return (hval%TableSize); } HashTbl *InitHash( int TableSize ) { int i; HashTbl *HashTable; HashTable = malloc(sizeof(HashTbl)); if( NULL == HashTable ) { printf("HashTable malloc error\n"); return; } HashTable->TableSize = TableSize; HashTable->Thelists = malloc(sizeof(List)*TableSize); if( NULL == HashTable->Thelists ) { printf("HashTable malloc error\n"); return; } for( i = 0; i < TableSize; i++) { HashTable->Thelists[i] = malloc(sizeof(ListNode)); if( NULL == HashTable->Thelists[i] ) { printf("HashTable malloc error\n"); return; } else { HashTable->Thelists[i]->next = NULL; HashTable->Thelists[i]->prev = NULL; } } return HashTable; } Position Find( void *key, HashTbl *HashTable ) { int i,j; List L; Position P; i = Hash(key,HashTable->TableSize); L = HashTable->Thelists[i]; P = L->next; while( P != NULL && P->data != key ) P = P->next; return P; } void Insert( void *key, HashTbl *HashTable ) { Position P,tmp; List L; P = Find(key,HashTable); if( NULL == P ) { tmp = malloc(sizeof(ListNode)); if( NULL == tmp ) { printf("malloc error\n"); return; } L = HashTable->Thelists[Hash(key,HashTable->TableSize)]; tmp->data = key; tmp->next = L->next; if(L->next != NULL) L->next->prev = tmp; tmp->prev = L; L->next = tmp; } else printf("the key already exist\n"); } void *Retrieve( Position P ) { return P->data; } void Destory( HashTbl *HashTable ) { int i; List L; Position tmp,tmp2; for( i = 0; i < HashTable->TableSize; i++) { L = HashTable->Thelists[i]; tmp = L->next; while(tmp->next != NULL) { tmp2 = tmp->next; free(tmp); tmp = tmp2; } free(L); } free(HashTable); } void main( void ) { HashTbl *HashTable; HashTable = InitHash(31); Insert("a",HashTable); Insert("b",HashTable); Insert("b",HashTable); Position P; P = Find("a",HashTable); printf("%s\n",Retrieve(P)); }
http://blog.csdn.net/dndxhej/article/details/7396841git
哈希表(hash table)是從一個集合A到另外一個集合B的映射(mapping)。映射是一種對應關係,並且集合A的某個元素只能對應集合B中的一個元素。但反過來,集合B中的一個元素可能對應多個集合A中的元素。若是B中的元素只能對應A中的一個元素,這樣的映射被稱爲一一映射。這樣的對應關係在現實生活中很常見,好比: 算法
A -> B 數據庫
人 -> 身份證號 數組
日期 -> 星座 安全
上面兩個映射中,人 -> 身份證號是一一映射的關係。在哈希表中,上述對應過程稱爲hashing。A中元素a對應B中元素b,a被稱爲鍵值(key),b被稱爲a的hash值(hash value)。 網絡
韋小寶的hash值 數據結構
映射在數學上至關於一個函數f(x):A->B。好比 f(x) = 3x + 2。哈希表的核心是一個哈希函數(hash function),這個函數規定了集合A中的元素如何對應到集合B中的元素。好比: app
A: 三位整數 hash(x) = x % 10 B: 一位整數 dom
104 4
876 6
192 2
上述對應中,哈希函數表示爲hash(x) = x % 10。也就是說,給一個三位數,咱們取它的最後一位做爲該三位數的hash值。
哈希表在計算機科學中應用普遍。好比:
Ethernet中的FCS:參看小喇叭開始廣播 (以太網與WiFi協議)
IP協議中的checksum:參看我盡力 (IP協議詳解)
git中的hash值:參看版本管理三國志
上述應用中,咱們用一個hash值來表明鍵值。好比在git中,文件內容爲鍵值,並用SHA算法做爲hash function,將文件內容對應爲固定長度的字符串(hash值)。若是文件內容發生變化,那麼所對應的字符串就會發生變化。git經過比較較短的hash值,就能夠知道文件內容是否發生變更。
再好比計算機的登錄密碼,通常是一串字符。然而,爲了安全起見,計算機不會直接保存該字符串,而是保存該字符串的hash值(使用MD五、SHA或者其餘算法做爲hash函數)。當用戶下次登錄的時候,輸入密碼字符串。若是該密碼字符串的hash值與保存的hash值一致,那麼就認爲用戶輸入了正確的密碼。這樣,就算黑客闖入了數據庫中的密碼記錄,他能看到的也只是密碼的hash值。上面所使用的hash函數有很好的單向性:很難從hash值去推測鍵值。所以,黑客沒法獲知用戶的密碼。
(以前有報道多家網站用戶密碼泄露的時間,就是由於這些網站存儲明文密碼,而不是hash值,見多家網站捲入CSDN泄密事件 明文密碼成爭議焦點)
注意,hash只要求從A到B的對應爲一個映射,它並無限定該對應關係爲一一映射。所以會有這樣的可能:兩個不一樣的鍵值對應同一個hash值。這種狀況叫作hash碰撞(hash collision)。好比網絡協議中的checksum就可能出現這種情況,即所要校驗的內容與原文並不一樣,但與原文生成的checksum(hash值)相同。再好比,MD5算法經常使用來計算密碼的hash值。已經有實驗代表,MD5算法有可能發生碰撞,也就是不一樣的明文密碼生成相同的hash值,這將給系統帶來很大的安全漏洞。(參考hash collision)
hash表被普遍的用於搜索。設定集合A爲搜索對象,集合B爲存儲位置,利用hash函數將搜索對象與存儲位置對應起來。這樣,咱們就能夠經過一次hash,將對象所在位置找到。一種常見的情形是,將集合B設定在數組下標。因爲數組能夠根據數組下標進行隨機存取(random access,算法複雜度爲1),因此搜索操做將取決於hash函數的複雜程度。
好比咱們以人名(字符串)爲鍵值,以數組下標爲hash值。每一個數組元素中存儲有一個指針,指向記錄 (有人名和電話號碼)。
下面是一個簡單的hash函數:
#define HASHSIZE 1007 /* By Vamei * hash function */ int hash(char *p) { int value=0; while((*p) != '\0') { value = value + (int) (*p); // convert char to int, and sum p++; } return (value % HASHSIZE); // won's exceed HASHSIZE }
hash value of "Vamei": 498
hash value of "Obama": 480
咱們能夠創建一個HASHSIZE大小的數組records,用於儲存記錄。HASHSIZE被選擇爲質數,以便hash值能更加均勻的分佈。在搜索"Vamei"的記錄時,能夠通過hash,獲得hash值498,再直接讀取records[498],就能夠讀取記錄了。
(666666是Obama的電話號碼,111111是Vamei的電話號碼。純屬杜撰,請勿當真)
hash搜索
若是不採用hash,而只是在一個數組中搜索的話,咱們須要依次訪問每一個記錄,直到找到目標記錄,算法複雜度爲n。咱們能夠考慮一下爲何會有這樣的差異。數組雖然能夠隨機讀取,但數組下標是隨機的,它與元素值沒有任何關係,因此咱們要逐次訪問各個元素。經過hash函數,咱們限定了每一個下標位置可能存儲的元素。這樣,咱們利用鍵值和hash函數,就能夠具有至關的先驗知識,來選擇適當的下標進行搜索。在沒有hash碰撞的前提下,咱們只須要選擇一次,就能夠保證該下標指向的元素是咱們想要的元素。
hash函數須要解決hash衝突的問題。好比,上面的hash函數中,"Obama"和"Oaamb"有相同的hash值,發生衝突。咱們如何解決呢?
一個方案是將發生衝突的記錄用鏈表儲存起來,讓hash值指向該鏈表,這叫作open hashing:
open hashing
咱們在搜索的時候,先根據hash值找到鏈表,再根據key值遍歷搜索鏈表,直到找到記錄。咱們能夠用其餘數據結構代替鏈表。
open hashing須要使用指針。咱們有時候想要避免使用指針,以保持隨機存儲的優點,因此採用closed hashing的方式來解決衝突。
closed hashing
這種狀況下,咱們將記錄放入數組。當有衝突出現的時候,咱們將衝突記錄放在數組中依然閒置的位置,好比圖中Obama被插入後,隨後的Oaamb也被hash到480位置。但因爲480被佔據,Oaamb探測到下一個閒置位置(經過將hash值加1),並記錄。
closed hashing的關鍵在如何探測下一個位置。上面是將hash值加1。但也能夠有其它的方式。歸納的說,在第i次的時候,咱們應該探測POSITION(i)=(h(x) + f(i)) % HASHSIZE的位置。上面將hash值加1的方式,就至關於設定f(i) = 1。當咱們在搜索的時候,就能夠利用POSITION(i),依次探測記錄可能出現的位置,直到找到記錄。
(f(i)的選擇會帶來不一樣的結果,這裏再也不深刻)
若是數組比較滿,那麼closed hashing須要進行許屢次探測才能找到空位。這樣將大大減少插入和搜索的效率。這種狀況下,須要增大HASHSIZE,並將原來的記錄放入到新的比較大的數組中。這樣的操做稱爲rehashing。
http://www.cnblogs.com/vamei/archive/2013/03/24/2970339.html