資料結構之散列表(雜湊表)
今天學的是資料結構的雜湊查詢篇,其他的查詢可參見以前的傳送門
以前的查詢都是基於比較關鍵字的基礎上,所以查詢的效率依賴於查詢過程中所進行的比較次數。
理想的情況是不經過任何比較,通過計算就能直接得到記錄所在的儲存地址,雜湊查詢(Hashed Search)是基於上述思想的一種查詢方式。
雜湊法又稱為雜湊法、雜湊法或關鍵字地址計演算法,是一種重要的儲存方式,又是一種查詢方式。
按雜湊法儲存方式構造的動態查詢表稱為散列表(Hash Table)。雜湊法查詢的核心是雜湊函式,又稱為雜湊函式。
雜湊法查詢的基本思想
以記錄中關鍵字的值K為自變數,通過確定的雜湊函式H進行計算,求出對應的函式值H(k),並吧這個函式值作為關鍵字值為K的記錄的儲存地址,將該記錄(或記錄的關鍵字)存放在這個位置上,查詢時仍按這個確定的雜湊函式H進行計算,獲得的將是待查詢關鍵字所在記錄的儲存地址。這樣,每個記錄的關鍵字通過雜湊函式H計算都將對應得到一個記錄的儲存地址:
Addr( i )=H (第 i 個記錄的關鍵字Ki) 其中H為確定的雜湊函式:Addr( i )為計算得到的第i 個記錄的儲存地址。
散列表查詢需要解決好以下兩個問題:
1、如何設計較好的雜湊函式。一個好的雜湊函式應該計算簡單(加快轉換速度);並且衝突較少,使雜湊函式結果值均勻分佈在散列表的地址空間中。
2、如何處理衝突
雜湊函式的構造方法
1、直接定址法
此法的雜湊函式是線性的,取關鍵字或關鍵字的某個線性函式值為雜湊地址2、除留餘數法( 最常用)
取關鍵字被某個不大於散列表表長m的質數p除後所得餘數為雜湊地址,即對關鍵字進行取餘運算:H( k )=k%p (p<=m)3、數字分析法
設關鍵字集合中,每個關鍵字均由m位組成,每位上可能有r種不同的符號。數字分析法根據r種不同的符號在各位上的分佈情況,選取某幾位,組合成雜湊地址。所選的位應使各種符號在該位上出現的頻率大致相同。(適用於關鍵字集中的集合,且關鍵字是事先知道的)4、平方取中法
若已知關鍵字為數字,但預先不一定能夠知道關鍵字的全部情況,用數字分析法難以確定哪幾位分佈比較均勻,可先求出關鍵字的平方,然後取其中若干位作為雜湊地址。(關鍵字平方後的結果與關鍵字中每一位都相關,故不同關鍵字產生不同雜湊地址的概率較高。
發生衝突是指由關鍵字得到的雜湊地址的位置上已經存有記錄。而處理衝突是為該關鍵字的記錄找到一個空的雜湊地址。在找空的雜湊地址時,可能還會產生衝突,此時需再找下一個空的雜湊地址,直到不產生衝突為止。
處理衝突法
1、開放定址法(再雜湊法)
分為:線性探測法、二次探測法
說白了就是本來的位置已經滿了,此時繼續以當前位置為起點往後尋找到空位,填入。2、拉鍊法(鏈地址法)
即將同一地址的關鍵字組成連結串列,放到該地址處3、建立一個公共溢位區
將散列表分為基本表和溢位表兩部分,凡是與基本表發生衝突的元素一律填入溢位表
演算法實現(完整版)
以除留餘數法分配空間,以線性探測法處理衝突。
#include<stdio.h>
#include<malloc.h>
typedef struct
{
int key;
char data;
}record;
int prime(int m)
{
int i,p,flag;
for(p=m;p>=2;p--)
{
for(i=2,flag=1;i<=p/2&&flag;i++)
if(p%i==0)
flag=0;
if(flag==1)
break;
}
return p;
}
int hi(int key,int p)
{
return key%p;
}
void creat(record**r,int n)
{
int i;
(*r)=(record*)malloc(n*sizeof(record));
printf("input %d number:\n",n);
for(i=0;i<n;i++)
scanf("%d",&((*r)[i].key));
}
void hashed(record**ht,record*r,int n,int m,int p)
{
int i,j;
(*ht)=(record*)malloc(m*sizeof(record));
for(i=0;i<m;i++)
(*ht)[i].key=0;
for(i=0;i<n;i++)
{
j=hi(r[i].key,p);
while((*ht)[j].key!=0)
j=(j+1)%m;
(*ht)[j].key=r[i].key;
}
}
int search(record*ht,int key,int p,int*k)
{
int i;
*k=1;
i=hi(key,p);
while(ht[i].key!=0&&ht[i].key!=key)
{
i++;
++*k;
}
if(ht[i].key==0)
return -1;
else return i;
}
int main()
{
record*r,*ht;
int key,i,n,m,p,k;
char ch;
printf("input n and m.(n<=m):");
scanf("%d,%d",&n,&m);
creat(&r,n);
p=prime(m);
printf("the prime is:%d",p);
hashed(&ht,r,n,m,p);
printf("\nthe hashed is:");
printf("\n location:");
for(i=0;i<m;i++)
printf("%3d",i);
printf("\nthe value:");
for(i=0;i<m;i++)
printf("%3d",ht[i].key);
do{
printf("\ninput search value:");
scanf("%d",&key);
i=search(ht,key,p,&k);
if(i!=-1)
{
printf("search good,location:%d",i);
printf("\n compare time:%d\n",k);
}
else {
printf("search bad");
printf("\n compare time:%d\n",k);
}
fflush(stdin);
ch=getchar();
}while(ch=='y'||ch=='Y');
return 0;
}
效能分析
雖然散列表時基於計算式的查詢方式但由於衝突的存在,雜湊法仍需進行關鍵字比較,故仍需要用平均查詢長度來評價雜湊法的查詢效能。影響雜湊法中關鍵字的比較次數的因素有三個:雜湊函式、處理衝突的方法、散列表的裝填因子。
散列表的裝填因子α=表中已記錄數/表長。α可描述散列表的裝填狀態程度。α越大,衝突越大;α越小,衝突越小。
總結
資料結構是一門神奇的學科,它的思想很豐富,應用也是相當之廣泛,有時間我將會多多更新其中的實際應用,資料結構就先告一段落了,開始偉大的自學奮鬥模式了。