在這裡沒有新的原創性的東西。該部分內容主要取材於《軟體設計師教程》部分的內容。
我想強調一種資料結構,散列表。它是基於快速存取的角度設計的,也是一種典型的“空間換時間”的做法。顧名思義,該資料結構可以理解為一個線性表,但是其中的元素不是緊密排列的,而是可能存在空隙。也就是說,比如我們儲存70個元素,但我們可能為這70個元素申請了100個元素的空間。70/100=0.7,這個數字稱為負載因子。我們之所以這樣做,也是為了“快速存取”的目的。我們基於一種結果儘可能隨機平均分布的固定函數H為每個元素安排儲存位置,這樣就可以避免遍曆性質的線性搜尋,以達到快速存取。但是由於此隨機性,也必然導致一個問題就是衝突。所謂衝突,即兩個元素通過散列函數H得到的地址相同,那麼這兩個元素稱為“同義字”。這類似於70個人去一個有100個椅子的飯店吃飯。散列函數的計算結果是一個儲存單位地址,每個儲存單位稱為“桶”。設一個散列表有m個桶,則散列函數的範圍應為[0,m-1]。
解決衝突是一個複雜問題。衝突主要取決於:(1)散列函數,一個好的散列函數的值應儘可能平均分布。(2)處理衝突方法。(3)負載因子的大小。太大不一定就好,而且浪費空間嚴重,負載因子和散列函數是聯動的。
解決衝突的辦法:
(1)線性探查法:衝突後,線性向前試探,找到最近的一個空位置。缺點是會出現堆積現象。存取時,可能不是同義字的詞也位於探查序列,影響效率。
(2)雙散列函數法:在位置d衝突後,再次使用另一個散列函數產生一個與散列表桶容量m互質的數c,依次試探(d+n*c)%m,使探查序列跳躍式分布。
下面例子,為c語言一共32個關鍵字建立散列表,為簡單每個桶的容量為1,取負載因子=0.7,因此散列表大小32/0.7=47。雙散列函數解決衝突,補償函數H2的基數取和47互質的數為43。
主要散列函數H1,取每三個字母為一段,每個字母佔據一個位元組,摺疊累加,取餘演算法。H2取每兩個字母為一段疊加。
Code
#define P1 47
#define P2 43
/* Compute Hash */
int GetHashCode(char *key,int iSection,int iBase, int offset)
{
long k=0,d;
int c;
while(*key)
{
for(d=0,c=0; *key != '\0' && c < iSection; c++)
{
d=(d<<8)+(*key++);
}
k+=d;
}
return (k % iBase + offset);
}
/* Double Hash Function */
int H1(char *key)
{
return GetHashCode(key, 3, P1, 0);
}
int H2(char *key)
{
return GetHashCode(key, 2, P2, 1);
}
32個關鍵字,注意,volatile可能是使用不多的一個關鍵字,指示變數是易變的,禁止編譯器對取值進行最佳化。Code
char tbl[N][LEN];
char *kWord[]=
{
"auto", "break", "case", "char", "const",
"continue", "default", "do", "double", "else",
"enum", "extern", "float", "for", "goto",
"if", "int", "long", "register", "return",
"short", "signed", "sizeof", "static", "struct",
"switch", "typedef", "union", "unsigned", "void",
"volatile", "while"
};
對散列表進行儲存的作業碼。尋找代碼是類似的。用count數組記錄每個位置的衝突次數。Code
for(i=0;i< sizeof(kWord)/sizeof(kWord[0]); i++)
{
pos= H1(kWord[i]);
c = H2(kWord[i]);
while(tbl[pos][0]!='\0' && strcmp(tbl[pos],kWord[i]))
{
count[pos]++;
pos=(pos+c)%N;
printf(",%d",pos);
}
strcpy(tbl[pos],kWord[i]);
}
------------------------------------------------------------
儲存時,每個元素的探查序列如下(數字為數組元素索引值):
auto : 12
break : 11
case : 40
char : 41
const : 33
continue : 45
default : 37
do : 2
double : 15
else : 25
enum : 30
extern : 34
float : 23
for : 4
goto : 41,37,33,29
if : 4,26
int : 39
long : 23,13
register : 28
return : 16
short : 13,25,37,2,14
signed : 25,0
sizeof : 0,43
static : 18
struct : 38
switch : 15,18,21
typedef : 26,7
union : 6
unsigned : 11,38,18,45,25,5
void : 7,31
volatile : 33,17
while : 32
可見,最長的探查序列為6次。
-----------------------------------------------------
各元素在散列表中的分布如下:
0: signed 2: do 4: for 5: unsigned 6: union
7: typedef 11: break 12: auto 13: long 14: short
15: double 16: return 17: volatile 18: static 21: switch
23: float 25: else 26: if 28: register 29: goto
30: enum 31: void 32: while 33: const 34: extern
37: default 38: struct 39: int 40: case 41: char
43: sizeof 45: continue
存在衝突的位置的衝突計數如下:
count[ 0]=1 count[ 2]=1 count[ 4]=1 count[ 7]=1 count[11]=1
count[13]=1 count[15]=1 count[18]=2 count[23]=1 count[25]=3
count[26]=1 count[33]=2 count[37]=2 count[38]=1 count[41]=1
count[45]=1