散列技術是在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關系f,使得每個關鍵字key對應一個存儲位置f(key),adr = f(key)。查找時,根據這個確定的對應關系找到給定值key的映射f(key),若查找集合中存在這個記錄,則必定在f(key)的位置上。
這里我們把這種
對應關系f稱為散列函數,又稱為
哈希函數
(Hash)
.按這個思想,
采用散列技術將記錄存儲在一塊連續的存儲空間中,這塊連續的存儲空間稱為散列表或哈希表(Hash table)
。
關鍵字對應的記錄存儲位置稱為
散列地址
。
散列技術既是一種存儲方法,也是一種查找方法。
散列技術最適合求解的問題是查找與給定值相等的記錄。
散列函數設計原則: 簡單 、 均勻 、 存儲利用率高
兩個關鍵字 key1 ≠ key2,但是卻有 f(key1) = f(key2),這種現象我們稱為沖突(collision),並把key1和key2稱為這個散列函數的同義詞(synonym)。
散列函數設計原則:
計算簡單: 散列函數的計算時間不應該超過其他查找技術與關鍵字比較的時間。
散列地址分布均勻:盡量讓散列地址均勻地分布在存儲空間中,這樣可以保證存儲空間的有效利用,並減少為處理沖突而耗費的時間。
直接定址法 f(key) = a * key + b(a、b為常數)
取關鍵字的某個線性函數值為散列地址。(這樣的散列函數簡單、均勻、不會產生沖突;需要事先知道關鍵字的分布情況,適合查找表較小且連續的情況,較少使用)
數字分析法
抽取關鍵字的一部分來計算散列函數位置的方法。適合處理關鍵字位數比較大的情況,(如果事先知道關鍵字的分布且關鍵字的若干位分布比較均勻,就可以考慮用這個方法。)
平方取中法
計算關鍵字的平方,再取中間幾位做為關鍵字。eg:1234,平方1522756,取中間3為227為關鍵字。(平方取中法比較適合不知道關鍵字的分布,而位數又不是很大的情況。)
折疊法
將關鍵字從左到右分割成位數相等的幾部分,然后將這幾部分疊加求和,並按散列表表長,取后幾位作為散列表地址。(折疊法事先不需要知道關鍵字的分布,適合關鍵字位數較多的情況。)
除留余數法
此方法為最常用的構造散列函數的方法。對於散列表長為m的散列函數公式為:
f(key) = key mod p(p≤m)
這種方法不僅可以對關鍵字直接取模,也可在折疊、平方取中后再取模。**散列表長為m,通常p為小於或等於表長(最好接近m)的最小質數或不包含小於20質因子的合數。
隨機數法
選擇一個隨機數,取關鍵字的隨機函數值為它的散列地址,也就是
f(key) = random(key)
。這里random是隨機函數。(當關鍵字的長度不等時,采用這個方法構造散列函數是比較合適的)
選擇散列函數應該考慮事項:
1、計算散列地址所需時間
2、關鍵字的長度
3、散列表的大小
4、關鍵字的分布情況
5、記錄查找的頻率
處理散列沖突的方法
開放定址法
一旦發生了沖突,就去尋找下一個空的散列地址,知道散列表足夠大,空的散列表地址總能找到,並將記錄存入。
這種解決沖突的開放定址發稱為
線性探測法
。
fi(key) = (f(key) + di) MOD m (di = 1,2,3,...,m-1)
在發生沖突進行重新定址的過程中會導致后面不是同義詞的關鍵字( f(keyi) ≠ f(key2) )爭奪一個地址的情況,這種現象為堆積。
線性探測都是發生沖突后加上一個di然后取余數來尋找下一個空間地址,如果發生沖突的位置之前就有一個空位置,要找到這個空位置要循環效率就很低
fi(key) = (f(key) + di) MOD m (di = 1^2,-1^2,2^2,...,q^2,-q^2,q≤m/2)
增加平方運算的目的是為了不讓關鍵字都聚集在某一塊區域,這種方法叫做
二次探測法
。
在沖突時,對於位移量di采用隨機函數計算得到。
fi(key) = (f(key) + di) MOD m (di 是一個偽隨機數列)
。 偽隨機,只要隨機種子相同,每次得到的數列都會是相同的。這就是
隨機探測法
再散列函數法
fi(key) = RHi(key) (i=1,2,...,k)
其中RHi就是不同的散列函數,每當發送沖突時,就換一個散列函數計算,總有一個可以解決沖突
鏈地址法:將所有關鍵字為同義詞的記錄存儲在一個單鏈表中,這種鏈表叫做同義詞子表,使用除留余數法,就不存在沖突的問題了,只是在鏈表中增加一個結點。 |
公共溢出區法
:
對所有沖突的關鍵字建立一個公共溢出區來存放
|
|
|
#include<iostream>
#include<stdlib.h>
#define max ~( 1<<(sizeof(int)*8-1) )
using namespace std;
//散列函數采用除留余數法
//沖突解決采用開放定址法的線性探測法
int hashFunc(int key,int length);
int initHashTable(int* hashTable,int length);
//成功返回0,失敗返回-1
int hashInsert(int* hashTable,int key,int length);
//成功返回0,失敗返回-1
static enum status{failture=-1,success=0} flag;
int hashSearch(int*hashTable,int key,int length);
void test();
int main()
{
test();
system("pause");
}
int initHashTable(int* hashTable,int length)
{
if(nullptr==hashTable || length<=0)
return -1;
for(int idx=0;idx!=length;++idx)
{
hashTable[idx] = max;
}
return 0;
}
int hashFunc(int key,int length)
{
if(key==max||length<=0)
return -1;
return key % length;
//除留余數
}
int hashInsert(int* hashTable,int key,int length)
{
if(nullptr==hashTable||length<=0)
return -1;
int hashAddr = hashFunc(key,length);
if(-1==hashAddr)
return -1;
for(int idx=0;idx!=length;++idx)
//循環,最大哈希表長度
{
if(hashTable[hashAddr]!=max)
//沖突
hashAddr = (hashAddr+1) % 12;
//開放定址法的線性探測法,查找下一個可存放數據的空間
else
break;
}
if(max==hashTable[hashAddr])
{
hashTable[hashAddr] = key;
return 0;
}
return -1;
}
|
void test()
{
int arr[12] = {12,67,56,16,25,37,22,29,15,47,48,34};
int* hashTable = new int[12]();
int ret = initHashTable(hashTable,12);
if(-1==ret)
cout<<"initHashTable fail!"<<endl;
cout<<"哈希表待插入元素:";
for(int idx=0;idx!=12;++idx)
{
cout<<arr[idx]<<" ";
hashInsert(hashTable,arr[idx],12);
}
cout<<endl;
cout<<"哈希表:";
for(int idx=0;idx!=12;++idx)
{
cout<<hashTable[idx]<<" ";
}
cout<<endl;
cout<<"對應插入元素序列在哈希表查找元素:";
for(int idx=0;idx!=12;++idx)
{
int ret = hashSearch(hashTable,arr[idx],12);
if( ret==-1 && flag == failture)
{
cout<<"search "<<arr[idx]<<" fail"<<endl;
}
cout<<hashTable[ret]<<" ";
}
cout<<endl;
cout<<"查找1:"<<endl;
int rett = hashSearch(hashTable,1,12);
if( rett==-1 && flag == failture)
{
cout<<"search "<<1<<" fail"<<endl;
return ;
}
cout<<hashTable[rett]<<endl;
}
int hashSearch(int*hashTable,int key,int length)
{
flag = success;
if(nullptr==hashTable||length<=0)
{
flag = failture;
return -1;
}
int hashAddr = hashFunc(key,length);
while(key!=hashTable[hashAddr])
{
hashAddr = (hashAddr+1) % length;
if(max==hashTable[hashAddr] || hashAddr == hashFunc(key,length))
//如果探測到下一個地址為空,還沒有找到,或者循環找了一遍又回到最開始的hashAddr
{
flag = failture;
return -1;
}
}
return hashAddr;
}
|