hash+鏈表

簡單的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與搜索

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

相關文章
相關標籤/搜索