转载自:http://blog.csdn.net/liuben/archive/2008/04/14/2290500.aspx
1、基本概念
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
2、常用的构造散列函数的方法
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。散列表的常用构造方法有:
(1)直接定址法
(2)数字分析法
(3)平方取中法
(4)折叠法
(5)随机数法
(6)除留余数法
3、处理冲突的方法
散列表函数设计好的情况下,可以减少冲突,但是无法完全避免冲突。常见有冲突处理方法有:
(1)开放定址法
(2)再散列法
(3)链地址法(拉链法)
(4)建立一个公共溢出区
4、散列表查找性能分析
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度。
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
(以上内容的详细介绍可以参见参考文献1。)
5、一个散列表实例
"The C Programming Language"一书中给出了一个散列表例子。它的实现代码很典型,可以在宏处理器或编译器的符号表管理例程中找到。完整的C代码如下:
01
#include
02
#include
03
04
#define HASHSIZE 101
05
06
struct
nlist {
07
struct
nlist *next;
08
char
*keys;
09
char
*value;
10
};
11
12
static
struct
nlist *hashtable[HASHSIZE];
13
14
unsigned hash(
char
*s)
15
{
16
unsigned hashval;
17
18
for
(hashval = 0; *s !=
''
; s++)
19
hashval = *s + 31 * hashval;
20
return
hashval % HASHSIZE;
21
}
22
23
struct
nlist *hashtable_search(
char
*s)
24
{
25
struct
nlist *np;
26
27
for
(np = hashtable[hash(s)]; np != NULL; np = np->next)
28
if
(
strcmp
(s, np->keys) == 0)
29
return
np;
30
return
NULL;
31
}
32
33
struct
nlist *hashtable_insert(
char
*keys,
char
*value)
34
{
35
struct
nlist *np;
36
unsigned hashval;
37
38
if
((np = hashtable_search(keys)) == NULL) {
39
np = (
struct
nlist *)
malloc
(
sizeof
(*np));
40
if
(np == NULL || (np->keys = strdup(keys)) == NULL)
41
return
NULL;
42
hashval = hash(keys);
43
np->next = hashtable[hashval];
44
hashtable[hashval] = np;
45
}
else
{
46
free
((
void
*)np->value);
47
}
48
49
if
((np->value = strdup(value)) == NULL)
50
return
NULL;
51
return
np;
52
}
53
54
char
*hashtable_getvalue(
char
*keys)
55
{
56
struct
nlist *np;
57
58
if
((np = hashtable_search(keys)) == NULL)
59
return
NULL;
60
else
61
return
np->value;
62
}
63
64
int
main(
int
argc,
char
*argv[])
65
{
66
char
*ret;
67
68
hashtable_insert(
"INT_MAX"
,
"32767"
);
69
hashtable_insert(
"INT_MIN"
,
"-32768"
);
70
hashtable_insert(
"LONG_MAX"
,
"2147483647"
);
71
hashtable_insert(
"LONG_MIN"
,
"-2147483647"
);
72
73
if
((ret = hashtable_getvalue(argv[1])) == NULL)
74
printf
(
"%s not found "
, argv[1]);
75
else
76
printf
(
"%s = %s "
, argv[1], ret);
77
}
散列函数hash,它通过一个for循环进行计算,每次循环中,它将上一次循环中计算得到的结果经过变换(即乘以31)后得到的新值同字符中的当前字符的值相加(*s + 31 * hashval),然后将该结果值同数据长度执行取模操作,其结果即是该函数的返回值。这个散列函数并不是最好的,但比较简短有效。另外,上面代码中采用链地址法来处理冲突,对桶大小未作限制。
这个散列函数到底是否真的简短有效呢?我们使用C语言中的保留关键字对其进行分析和测试。C语言的保留关键字有32个(如下面代码中定义),散列表长度为101。因此装填因子
α = 32 /101 = 0.32
这个装填因子比较小,从理论上说冲突的可能性较小,但牺牲了较多的空间,以空间换取了效率。
我们使用如下的程序对C语言保留关键字进行hash计算:
01
#define HASHSIZE 101
02
unsigned hash(
char
*s)
03
{
04
unsigned hashval;
05
06
for
(hashval = 0; *s !=
''
; s++)
07
hashval = *s + 31 * hashval;
08
return
hashval % HASHSIZE;
09
}
10
11
char
*keywords[] = {
12
"auto"
,
"break"
,
"case"
,
"char"
,
"const"
,
"continue"
,
"default"
,
"do"
,
13
"double"
,
"else"
,
"enum"
,
"extern"
,
"float"
,
"for"
,
"goto"
,
"if"
,
14
"int"
,
"long"
,
"register"
,
"return"
,
"short"
,
"signed"
,
"sizeof"
,
"static"
,
15
"struct"
,
"switch"
,
"typedef"
,
"union"
,
"unsigned"
,
"void"
,
"volatile"
,
"while"
16
};
17
18
int
main(
void
) {
19
int
i, size, pos;
20
int
count[HASHSIZE];
21
22
for
(i = 0; i < HASHSIZE; i++)
23
count[i] = 0;
24
25
size =
sizeof
(keywords) /
sizeof
(keywords[0]);
26
for
(i = 0;i < size; i++)
27
count[hash(keywords[i])]++;
28
29
for
(i = 0; i < size; i++){
30
pos = hash(keywords[i]);
31
printf
(
"%-10s: %-3d %d "
, keywords[i], pos, count[pos]);
32
}
33
return
0;
34
}
我们可以得到如下的输出结果:
auto : 10 1
break : 0 1
case : 32 1
char : 53 1
const : 14 1
continue : 87 1
default : 17 1
do : 80 1
double : 76 1
else : 91 1
enum : 63 1
extern : 16 1
float : 57 1
for : 72 1
goto : 78 1
if : 24 1
int : 98 1
long : 66 1
register : 49 1
return : 96 1
short : 99 1
signed : 81 1
sizeof : 51 1
static : 85 1
struct : 1 1
switch : 5 1
typedef : 2 1
union : 22 1
unsigned : 4 1
void : 70 1
volatile : 71 1
while : 100 1
从这个结果可以看出,散列表中的数据分布比较均匀,而且更为理想的是,居然没有发生一个冲突。可见,书中所说的“简短有效”确实名副其实,甚至是非常理想的散列函数。这使得我不禁又要推荐“C程序设计语言”一书啦
HashTable的c语言实现
转载自:http://blog.csdn.net/liuben/archive/2008/04/14/2290500.aspx
1、基本概念
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
2、常用的构造散列函数的方法
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。散列表的常用构造方法有:
(1)直接定址法
(2)数字分析法
(3)平方取中法
(4)折叠法
(5)随机数法
(6)除留余数法
3、处理冲突的方法
散列表函数设计好的情况下,可以减少冲突,但是无法完全避免冲突。常见有冲突处理方法有:
(1)开放定址法
(2)再散列法
(3)链地址法(拉链法)
(4)建立一个公共溢出区
4、散列表查找性能分析
散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度。
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。
(以上内容的详细介绍可以参见参考文献1。)
5、一个散列表实例
"The C Programming Language"一书中给出了一个散列表例子。它的实现代码很典型,可以在宏处理器或编译器的符号表管理例程中找到。完整的C代码如下:
01
#include
02
#include
03
04
#define HASHSIZE 101
05
06
struct
nlist {
07
struct
nlist *next;
08
char
*keys;
09
char
*value;
10
};
11
12
static
struct
nlist *hashtable[HASHSIZE];
13
14
unsigned hash(
char
*s)
15
{
16
unsigned hashval;
17
18
for
(hashval = 0; *s !=
''
; s++)
19
hashval = *s + 31 * hashval;
20
return
hashval % HASHSIZE;
21
}
22
23
struct
nlist *hashtable_search(
char
*s)
24
{
25
struct
nlist *np;
26
27
for
(np = hashtable[hash(s)]; np != NULL; np = np->next)
28
if
(
strcmp
(s, np->keys) == 0)
29
return
np;
30
return
NULL;
31
}
32
33
struct
nlist *hashtable_insert(
char
*keys,
char
*value)
34
{
35
struct
nlist *np;
36
unsigned hashval;
37
38
if
((np = hashtable_search(keys)) == NULL) {
39
np = (
struct
nlist *)
malloc
(
sizeof
(*np));
40
if
(np == NULL || (np->keys = strdup(keys)) == NULL)
41
return
NULL;
42
hashval = hash(keys);
43
np->next = hashtable[hashval];
44
hashtable[hashval] = np;
45
}
else
{
46
free
((
void
*)np->value);
47
}
48
49
if
((np->value = strdup(value)) == NULL)
50
return
NULL;
51
return
np;
52
}
53
54
char
*hashtable_getvalue(
char
*keys)
55
{
56
struct
nlist *np;
57
58
if
((np = hashtable_search(keys)) == NULL)
59
return
NULL;
60
else
61
return
np->value;
62
}
63
64
int
main(
int
argc,
char
*argv[])
65
{
66
char
*ret;
67
68
hashtable_insert(
"INT_MAX"
,
"32767"
);
69
hashtable_insert(
"INT_MIN"
,
"-32768"
);
70
hashtable_insert(
"LONG_MAX"
,
"2147483647"
);
71
hashtable_insert(
"LONG_MIN"
,
"-2147483647"
);
72
73
if
((ret = hashtable_getvalue(argv[1])) == NULL)
74
printf
(
"%s not found "
, argv[1]);
75
else
76
printf
(
"%s = %s "
, argv[1], ret);
77
}
散列函数hash,它通过一个for循环进行计算,每次循环中,它将上一次循环中计算得到的结果经过变换(即乘以31)后得到的新值同字符中的当前字符的值相加(*s + 31 * hashval),然后将该结果值同数据长度执行取模操作,其结果即是该函数的返回值。这个散列函数并不是最好的,但比较简短有效。另外,上面代码中采用链地址法来处理冲突,对桶大小未作限制。
这个散列函数到底是否真的简短有效呢?我们使用C语言中的保留关键字对其进行分析和测试。C语言的保留关键字有32个(如下面代码中定义),散列表长度为101。因此装填因子
α = 32 /101 = 0.32
这个装填因子比较小,从理论上说冲突的可能性较小,但牺牲了较多的空间,以空间换取了效率。
我们使用如下的程序对C语言保留关键字进行hash计算:
01
#define HASHSIZE 101
02
unsigned hash(
char
*s)
03
{
04
unsigned hashval;
05
06
for
(hashval = 0; *s !=
''
; s++)
07
hashval = *s + 31 * hashval;
08
return
hashval % HASHSIZE;
09
}
10
11
char
*keywords[] = {
12
"auto"
,
"break"
,
"case"
,
"char"
,
"const"
,
"continue"
,
"default"
,
"do"
,
13
"double"
,
"else"
,
"enum"
,
"extern"
,
"float"
,
"for"
,
"goto"
,
"if"
,
14
"int"
,
"long"
,
"register"
,
"return"
,
"short"
,
"signed"
,
"sizeof"
,
"static"
,
15
"struct"
,
"switch"
,
"typedef"
,
"union"
,
"unsigned"
,
"void"
,
"volatile"
,
"while"
16
};
17
18
int
main(
void
) {
19
int
i, size, pos;
20
int
count[HASHSIZE];
21
22
for
(i = 0; i < HASHSIZE; i++)
23
count[i] = 0;
24
25
size =
sizeof
(keywords) /
sizeof
(keywords[0]);
26
for
(i = 0;i < size; i++)
27
count[hash(keywords[i])]++;
28
29
for
(i = 0; i < size; i++){
30
pos = hash(keywords[i]);
31
printf
(
"%-10s: %-3d %d "
, keywords[i], pos, count[pos]);
32
}
33
return
0;
34
}
我们可以得到如下的输出结果:
auto : 10 1
break : 0 1
case : 32 1
char : 53 1
const : 14 1
continue : 87 1
default : 17 1
do : 80 1
double : 76 1
else : 91 1
enum : 63 1
extern : 16 1
float : 57 1
for : 72 1
goto : 78 1
if : 24 1
int : 98 1
long : 66 1
register : 49 1
return : 96 1
short : 99 1
signed : 81 1
sizeof : 51 1
static : 85 1
struct : 1 1
switch : 5 1
typedef : 2 1
union : 22 1
unsigned : 4 1
void : 70 1
volatile : 71 1
while : 100 1
从这个结果可以看出,散列表中的数据分布比较均匀,而且更为理想的是,居然没有发生一个冲突。可见,书中所说的“简短有效”确实名副其实,甚至是非常理想的散列函数。这使得我不禁又要推荐“C程序设计语言”一书啦