基本知識
Hash,一般翻譯做“散列”,也有直接音譯爲”哈希“的,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。
HASH主要用於信息安全領域中加密算法,他把一些不同長度的信息轉化成雜亂的128位的編碼裏,叫做HASH值. 也可以說,hash就是找到一種數據內容和數據存放地址之間的映射關係
基本概念
* 若結構中存在關鍵字和K相等的記錄,則必定在f(K)的存儲位置上。由此,不需比較便可直接取得所查記錄。稱這個對應關係f爲散列函數(Hash function),按這個思想建立的表爲散列表。
* 對不同的關鍵字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),這種現象稱衝突。具有相同函數值的關鍵字對該散列函數來說稱做同義詞。綜上所述,根據散列函數H(key)和處理衝突的方法將一組關鍵字映象到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的“象” 作爲記錄在表中的存儲位置,這種表便稱爲散列表,這一映象過程稱爲散列造表或散列,所得的存儲位置稱散列地址。
* 若對於關鍵字集合中的任一個關鍵字,經散列函數映象到地址集合中任何一個地址的概率是相等的,則稱此類散列函數爲均勻散列函數(Uniform Hash function),這就是使關鍵字經過散列函數得到一個“隨機的地址”,從而減少衝突。
常用的構造散列函數的方法
散列函數能使對一個數據序列的訪問過程更加迅速有效,通過散列函數,數據元素將被更快地定位ǐ
1. 直接尋址法:取關鍵字或關鍵字的某個線性函數值爲散列地址。即H(key)=key或H(key) = a•key + b,其中a和b爲常數(這種散列函數叫做自身函數)
2. 數字分析法
3. 平方取中法
4. 摺疊法
5. 隨機數法
6. 除留餘數法:取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。即 H(key) = key MOD p, p<=m。不僅可以對關鍵字直接取模,也可在摺疊、平方取中等運算之後取模。對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義詞。
處理衝突的方法
1. 開放尋址法;Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)爲散列函數,m爲散列表長,di爲增量序列,可有下列三種取法:
1. di=1,2,3,…, m-1,稱線性探測再散列;
2. di=1^2, (-1)^2, 2^2,(-2)^2, (3)^2, …, ±(k)^2,(k<=m/2)稱二次探測再散列;
3. di=僞隨機數序列,稱僞隨機探測再散列。 ==
2. 再散列法:Hi=RHi(key), i=1,2,…,k RHi均是不同的散列函數,即在同義詞產生地址衝突時計算另一個散列函數地址,直到衝突不再發生,這種方法不易產生“聚集”,但增加了計算時間。
3. 鏈地址法(拉鍊法)
4. 建立一個公共溢出區
查找的性能分析
散列表的查找過程基本上和造表過程相同。一些關鍵碼可通過散列函數轉換的地址直接找到,另一些關鍵碼在散列函數得到的地址上產生了衝突,需要按處理衝突的方法進行查找。在介紹的三種處理衝突的方法中,產生衝突後的查找仍然是給定值與關鍵碼進行比較的過程。所以,對散列表查找效率的量度,依然用平均查找長度來衡量。
查找過程中,關鍵碼的比較次數,取決於產生衝突的多少,產生的衝突少,查找效率就高,產生的衝突多,查找效率就低。因此,影響產生衝突多少的因素,也就是影響查找效率的因素。影響產生衝突多少有以下三個因素:
1. 散列函數是否均勻;
2. 處理衝突的方法;
3. 散列表的裝填因子。
散列表的裝填因子定義爲:α= 填入表中的元素個數 / 散列表的長度
α是散列表裝滿程度的標誌因子。由於表長是定值,α與“填入表中的元素個數”成正比,所以,α越大,填入表中的元素較多,產生衝突的可能性就越大;α越小,填入表中的元素較少,產生衝突的可能性就越小。
實際上,散列表的平均查找長度是裝填因子α的函數,只是不同處理衝突的方法有不同的函數。
字符串哈希函數(著名的ELFhash算法)
int ELFhash(char *key)
{ unsigned long h=0;
while(*key)
{ h=(h<<4)+*key++;
unsigned long g=h&0Xf0000000L;
if(g) h^=g>>24;
h&=~g;
}
return h%MOD;
}
下面是一個Hash表及其功能實現
typedef struct //定義哈希表的結構
{int elem[MAXSIZE]; //數據元素體
HAVEORNOT elemflag[MAXSIZE]; //元素狀態標誌,沒有記錄、有記錄、有過記錄但已被刪除
int count; //哈希表中當前元素的個數
}HashTable;
BOOL DeleteHash(HashTable &H,Record e)
{//在查找成功時刪除待刪元素e,並返回True,否則返回False
int &p;
if(!SearchHash(H,e.keynum,p)) //表中不存在待刪元素
return False;
else
{H.elemflag[p]=DELKEY; //設置標誌爲DELKEY,表明該元素已被刪除
H.count--; //哈希表當前長度減一
return True;
}
}
BOOL SearchHash(HashTable H,int k,int &p)
{//在開放定址哈希表H中查找關鍵字爲k的數據元素,若查找成功,以p指示
//待查數據元素在表中的位置,並返回True;否則,以p指示插入位置,並
//返回False
int &p1;
p1=p=Hash(k); //求得哈希地址
while(H.elemflag[p]==HAVEKEY&&k!=H.elem[p])
//該位置中填有記錄並且關鍵字不相等
{p++; //衝突處理方法:線性探測再散列
if(p>=MAXSIZE) p=p%MAXSIZE; //循環搜索
if(p==p1) return False; //整個表已搜索完,沒有找到待查元素
}
if(k==H.elem[p]&&H.elemflag[p]==HAVEKEY) //查找成功,p指示待查元素位置
return True;
else return False; //查找不成功
}
1、用數組
[c-sharp] view plaincopy
#include <stdio.h>
#define N 4
typedef int datatype;
typedef struct
{
datatype key;
}Hretype;
int LHashsearch(Hretype HT[N], datatype k)
{
int addr,i=0;
addr = k % N;
while(i<N && HT[addr].key != -1 && HT[addr].key != k)
{
i++;
addr = (addr+1)%N;
}
if(i == N)
return -1; //表溢出
else
return addr;
}
int LHinsert(Hretype HT[N], Hretype R)
{
int addr;
addr = LHashsearch(HT, R.key);
if(addr==-1 || HT[addr].key == R.key)
{
return 1;
}
else
{
HT[addr] = R;
return 0;
}
}
int main()
{
Hretype R[6];
Hretype HT[N];
for(int i=0;i<N;i++)
HT[i].key = -1;
for(i=0;i<6;i++)
R[i].key = i;
for(i=0;i<6;i++)
{
int value = LHinsert(HT,R[i]);
if(value)
printf("表溢出或記錄已存在!/n");
else
{
printf("插入成功!/n");
}
}
return 0;
}
2、用鏈表
[c-sharp] view plaincopy
#include <stdio.h>
#include <stdlib.h>
#define N 4
typedef int datatype;
typedef struct node
{
datatype key;
struct node *next;
}Renode;
Renode *LinkHsearch(Renode *HT[N], datatype k)
{
Renode *p;
int addr = k % N;
p = HT[addr];
while(p && p->key !=k)
p = p->next;
return p;
}
void LinkHinsert(Renode *HT[N], Renode *S)
{
int addr;
Renode *p;
p = LinkHsearch(HT,S->key);
if(p)
{
printf("記錄已存在/n");
free(S);
}
else
{
addr = S->key % N;
S->next = HT[addr];
HT[addr] = S;
printf("已插入/n");
}
}
int main()
{
Renode *HT[N];
//指針數組初始化
for(int i=0;i<N;i++)
HT[i] = NULL;
for(i=0;i<6;i++)
{
Renode *S = (Renode*)malloc(sizeof(Renode));
S->key = i;
LinkHinsert(HT,S);
}
for(i=0;i<6;i++)
{
Renode *S = (Renode*)malloc(sizeof(Renode));
S->key = i;
LinkHinsert(HT,S); //注意指針數組實參
}
return 0;
}