1. 程式人生 > >資料結構之散列表(雜湊表)

資料結構之散列表(雜湊表)

今天學的是資料結構的雜湊查詢篇,其他的查詢可參見以前的傳送門

以前的查詢都是基於比較關鍵字的基礎上,所以查詢的效率依賴於查詢過程中所進行的比較次數。
理想的情況是不經過任何比較,通過計算就能直接得到記錄所在的儲存地址,雜湊查詢(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;
}

這裡寫圖片描述

效能分析

雖然散列表時基於計算式的查詢方式但由於衝突的存在,雜湊法仍需進行關鍵字比較,故仍需要用平均查詢長度來評價雜湊法的查詢效能。影響雜湊法中關鍵字的比較次數的因素有三個:雜湊函式、處理衝突的方法、散列表的裝填因子。
散列表的裝填因子α=表中已記錄數/表長。α可描述散列表的裝填狀態程度。α越大,衝突越大;α越小,衝突越小。

總結

資料結構是一門神奇的學科,它的思想很豐富,應用也是相當之廣泛,有時間我將會多多更新其中的實際應用,資料結構就先告一段落了,開始偉大的自學奮鬥模式了。