散列表设计

散列表设计

(刘爱贵 - Aiguille.LIU)

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代码如下:
#include  < stdio.h >
#include 
< string .h >

#define  HASHSIZE 101

struct  nlist  {
  
struct nlist *next;
  
char *keys;
  
char *value;
}
;

static   struct  nlist  * hashtable[HASHSIZE];

unsigned hash(
char   * s)
{
  unsigned hashval;

  
for (hashval = 0*!= ''; s++)
    hashval 
= *+ 31 * hashval;
  
return hashval % HASHSIZE;
}


struct  nlist  * hashtable_search( char   * s)
{
  
struct nlist *np;

  
for (np = hashtable[hash(s)]; np != NULL; np = np->next)
    
if (strcmp(s, np->keys) == 0)
      
return np;
  
return NULL;
}


struct  nlist  * hashtable_insert( char   * keys,  char   * value)
{
  
struct nlist *np;
  unsigned hashval;

  
if ((np = hashtable_search(keys)) == NULL) {
    np 
= (struct nlist *)malloc(sizeof(*np));
    
if (np == NULL || (np->keys = strdup(keys)) == NULL)
      
return NULL;
    hashval 
= hash(keys);
    np
->next = hashtable[hashval];
    hashtable[hashval] 
= np;
  }
 else
    free((
void *)np->value);
  
if ((np->value = strdup(value)) == NULL)
    
return NULL;
  
return np;
}


char   * hashtable_getvalue( char   * keys)
{
  
struct nlist *np;

  
if ((np = hashtable_search(keys)) == NULL)
    
return NULL;
  
else 
    
return np->value;
}


int  main( int  argc,  char   * argv[])
{
  
char *ret;
  
  hashtable_insert(
"INT_MAX""32767");
  hashtable_insert(
"INT_MIN""-32768");
  hashtable_insert(
"LONG_MAX""2147483647");
  hashtable_insert(
"LONG_MIN""-2147483647");

  
if ((ret = hashtable_getvalue(argv[1])) == NULL) 
    printf(
"%s not found ", argv[1]);
  
else 
    printf(
"%s = %s ", argv[1], ret);
}


  散列函数hash,它通过一个for循环进行计算,每次循环中,它将上一次循环中计算得到的结果经过变换(即乘以31)后得到的新值同字符中的当前字符的值相加(*s + 31 * hashval),然后将该结果值同数据长度执行取模操作,其结果即是该函数的返回值。这个散列函数并不是最好的,但比较简短有效。另外,上面代码中采用链地址法来处理冲突,对桶大小未作限制。

  这个散列函数到底是否真的简短有效呢?我们使用C语言中的保留关键字对其进行分析和测试。C语言的保留关键字有32个(如下面代码中定义),散列表长度为101。因此装填因子
α = 32 /101 = 0.32
这个装填因子比较小,从理论上说冲突的可能性较小,但牺牲了较多的空间,以空间换取了效率。

我们使用如下的程序对C语言保留关键字进行hash计算:
#define  HASHSIZE 101
unsigned hash(
char   * s)
{
  unsigned hashval;

  
for (hashval = 0*!= ''; s++)
    hashval 
= *+ 31 * hashval;
  
return hashval % HASHSIZE;
}


char   * keywords[]  =   {
  
"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"
}
;

int  main( void {
  
int i, size, pos;
  
int count[HASHSIZE];
  
  
for(i = 0; i < HASHSIZE; i++)
     count[i] 
= 0;

  size 
= sizeof(keywords) / sizeof(keywords[0]);
  
for(i = 0;i < size; i++)
    count[hash(keywords[i])]
++;

  
for(i = 0; i < size; i++{
    pos 
= hash(keywords[i]);
    printf(
"%-10s: %-3d %d ", keywords[i], pos, count[pos]);
  }

  
return 0;
}


我们可以得到如下的输出结果:
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程序设计语言”一书啦!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值