哈希表(HashTable)

1. 哈希表綜述:
哈希表(Hash Table)也叫散列表,是根據關鍵碼值直接訪問
就是一個把關鍵碼映射到表中的一個位置來訪問記錄的過程
這個映射函數叫做哈希函數,用hash()表示,存放記錄的數組叫做哈希表(一個數組)
哈希表是一種高效的數據結構,主要體現在數據的查找上,幾乎可以看成常數時間
例如:對於排序算法,最低時間複雜度爲O(nlogn),但是對於一些特殊情況可以更快,現有N個整數(N>10000)範圍爲0-10000,如何排序?
建立一個數組int num[10000],初始化爲0,num[i]表示有多少個數等於i
這樣每讀入一個數x,num[x]++,最後num[0]-num[10000]依次取出這些數,複雜度爲O(n),
這個思想就是hash:將每個對象對應道一個關鍵值,然後按關鍵值歸類

2. 哈希表的衝突
處理哈希衝突的方法有:開散列和閉散列
1、開散列:也叫拉鍊法
通俗的說,就是既然元素a和元素b都該放在裏面只好擠一擠了,即每個位置存放所有該存放在裏面的元素。但是怎麼把很多元素放在一個位置呢?只要在位置上放一個鏈表表頭就可以了,該鏈表裏包含所有放在該位置的元素,在實際應用中,往往不把鏈表做成傳統的使用動態內存的結構,而是自己維護一個大數組,給鏈表元素分配數組下標,這樣又方便又節省時間空間
2、閉散列法:也叫開放地址法:
通俗的講,就是,既然a可以霸佔b的位置,b也可以霸佔c的位置,不嚴格按照關鍵值的hash碼來選擇位置,而是在位置被佔用時按照某種方法另選一個位置,哈希表是將某個對象對應道一個關鍵值上,可是,不同的對象可能對應道一個相同的關鍵值,這就叫做哈希衝突。哈希表的大小一般選取p(p爲小於表長的最大素數),如果出現兩個不同的數對應到同一關鍵值,例如0和p
注意:可以降哈希表的每一個位置做成一個鏈表,插入到鏈表即可,這叫開放散列法
重要:進行哈希查找的時候,先找到每個對象對應的關鍵值,如果這個關鍵值有多個對象對應,然後,在沿着這個關鍵值的鏈表依次查找對象

3. 哈希表的插入和查找
哈希表的插入和查找幾乎是一樣的即:
1、計算哈希函數值,得到對應位置 hash(k)
2、從hash(k)開始,使用(如果需要)衝突解決策略定位包含關鍵字K的記錄
3、如果需要插入,把數據插入即可
如果衝突可以忽略不計,兩種操作的時間複雜度爲O(1)

哈希函數的選取
怎樣選取好的hash函數纔可以使計算不過雨複雜,衝突又比較小呢?對於這個問題,只有一些經驗上的解決方式
對於數值
1、直接取餘數(一般選取M作爲除數,M最好是個質數)
2、平法取中,即計算關鍵字平方,再取中間r位形成一個大小爲2^r的表
分析:方法1容易產生分佈不均勻的情況
方法2好得多,因爲幾乎所有位都對結果產生了影響,使得計算量大,一般也很少使用
對於字符串
1、摺疊法:把所有字符的ACSII碼加起來
2、採用ELFHash()函數(它用於Unix的可執行鏈接格式,ELF中)
ELFHash()函數是一個很有用的Hash函數。對長短字符串都有效

unsigned int ELFHash(char *str)  
{
    unsigned int hash = 0;  
    unsigned int x = 0;  

    while (*str)  
    {
        //hash左移4位,把當前字符ASCII存入hash低四位
        hash = (hash << 4) + (*str++); 
        if ((x = hash & 0xF0000000L) != 0)  
        {
            //如果最高的四位不爲0,則說明字符多餘7個,現在正在存第7個字符
            //如果不處理,再加下一個字符時,第一個字符會被移出,因此要有如下處理。  
            //該處理,如果最高位爲0,就會僅僅影響5-8位,否則會影響5-31位,因爲C語言使用的算數移位  
            //因爲1-4位剛剛存儲了新加入到字符,所以不能>>28  
            hash ^= (x >> 24);  
            //上面這行代碼並不會對X有影響,本身X和hash的高4位相同,下面這行代碼&~即對28-31(高4位)位清零。  
            hash &= ~x;  
        }  
    }  
    //返回一個符號位爲0的數,即丟棄最高位,以免函數外產生影響。(我們可以考慮,如果只有字符,符號位不可能爲負)  
    return (hash & 0x7FFFFFFF);  
} 

4. 哈希表應用:
題意概述:
輸入幾組對應的字符串,其中一個是English,另一個是Foreign Language,開始輸入“字典”,然後根據Foreign Language查詢字典,如果沒有輸出eh

C++ Code:

#include <iostream>
#include <cstring>

#define MOD 10003       //槽數,最好是素數 
using namespace std;

//定義哈希表的每個節點
struct node
{
    int pos;    //存儲每個字符串在自己數組中的位置
    struct node* next; 
};
node* hash[MOD] = {NULL};
//定義字符串
char word[10000][11];
//定義對應字符串所屬字符串
char belong[10000][11]; 

//Unix系統字符串ELFHash散列函數 
int ELFHash(char* key)
{
    unsigned long h = 0, g;
    int i=0;
    while (key[i])
    {
        h = h<<4 + key[i]++;
        g = h & 0XF0000000L;
        if (g)
        {
            h ^= g >> 24;
        }
        h &= ~g;
    }
    return h%MOD;
}

int main()
{
    int index = 0;
    int hashkey = 0;
    node* p = NULL;
    //創建字典,str裏面既包含了word,也包含了belong,中間用空格分開 
    char str[50];
    gets(str); 
    while (strcmp(str, "end") != 0)
    {
        //截取word
        int i;
        for (i=0; str[i]!=' '; ++i)
            word[index][i] = str[i];
        word[index][i++] = '\0'; 
        //截取belong
        strcpy(belong[index], str+i); 
        hashkey = ELFHash(belong[index]);

        //這裏是處理哈希衝突,採用開散列法,也叫拉鍊法 
        //採用頭插 
        p = new node;
        p->pos = index;
        p->next = hash[hashkey];
        hash[hashkey] = p;

        ++index;

        //繼續輸入字符串
        gets(str);
    }

    //查詢
    cin >> str;
    hashkey = ELFHash(str);
    p = hash[hashkey];
    //處理衝突 
    while (NULL != p)
    {
        //找到 
        if (strcmp(belong[p->pos], str) == 0)
            break;
        p = p->next;
    }
    if (NULL == p)
        cout << "eh" << endl;
    else
        cout << word[p->pos] << endl; 

    return 0;
}
發佈了73 篇原創文章 · 獲贊 107 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章