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;
}