HASH表的实现

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,有点类似于数组,并且能在O(1)(冲突情况另算)下查找到元素。具体的介绍网上有很详细的描述 ,这里就不再累述了;

下面只是说一下几个关键问题:

1.哈希函数

也叫散列函数,即:根据key,计算出key对应记录的储存位置

position = f(key)

散列函数满足以下的条件:

1、对输入值运算,得到一个固定长度的摘要(Hash Value);

2、不同的输入值可能对应同样的输出值;

以下的函数都可以认为是一个散列函数:

f(x) = x mod 16; (1)

f(x) = (x 2 + 10) * x; (2)

f(x) = (x | 0×0000FFFF) XOR (x >> 16); (3)

不过,仅仅满足上面这两条的函数,作为散列函数,还有不足的地方。我们还希望散列函数满足下面几点:

1、散列函数的输出值尽量接近均匀分布;

2、x的微小变化可以使f(x)发生非常大的变化,即所谓“雪崩效应”( Avalanche effect );

上面两点用数学语言表示,就是:

1, 输出值y的分布函数F(y)=y/m, m为散列函数的最大值。或记为y~U[0, m]

2,|df(x)/dx| >> 1;

从上面两点,大家看看,前面举例的三个散列函数,哪个更好呢?对了,是第三个:

f(x) = (x | 0×0000FFFF) XOR (x >> 16);

它很完美地满足“好的散列函数”的两个附加条件。

2、哈希冲突(Hash collision)

也就是两个不同输入产生了相同输出值的情况。首先,哈希冲突是无法避免的,因此,哈希算法的选择直接决定了哈希冲突发送的概率;同时必须要对哈希冲突进行处理,方法主要有以下几种:  链地址法、 开放地址法等等。

下面就来看看每种方法的具体实现吧;

链地址法:

举例说明:设有 8 个元素 { a,b,c,d,e,f,g,h } ,采用某种哈希函数得到的地址分别为: {0 , 2 , 4 , 1 , 0 , 8 , 7 , 2} ,当哈希表长度为 10 时,采用链地址法解决冲突的;


下面是代码实现,已经测试过:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct node{
  char *name;//字段名
  char *desc;//描述
  struct node *next;
}node;
#define HASHSIZE 100 //hash表长度
static node* hashtable[HASHSIZE];//定义一个hash数组,该数组的每个元素是一个hash结点指针,并且由于是全局静态变量,默认初始化为NULL

unsigned int hash(char *s)
{//哈希函数
  unsigned int h=0;
  for(;*s;s++)
    h=*s+h*31;//将整个字符串按照特定关系转化为一个整数,然后对hash长度取余
  return h%HASHSIZE;
}
node* lookup(char *str)
{
  unsigned int hashvalue = hash(str);
  node* np = hashtable[hashvalue];
  for( ; np!=NULL; np = np->next)
  {//这里是链地址法解决的冲突,返回的是第一个链表结点
    if(!strcmp(np->name, str))//strcmp相等的时候才返回0
      return np;
  }
  return NULL;
}
char* search(char* name)
{//对hash表查找特定元素(元素是字符串)
  node* np=lookup(name);
  if(np==NULL)
    return NULL;
  else
    return np->desc;
}
node* malloc_node(char* name, char* desc)
{//在堆上为结点分配内存,并填充结点
  node *np=(node*)malloc(sizeof(node));
  if(np == NULL)
    return NULL;
  np->name = name;
  np->desc = desc;
  np->next = NULL;
  return np;
}
int insert(char* name, char* desc)
{
  unsigned int hashvalue;
  hashvalue = hash(name);
  //头插法,不管该hash位置有没有其他结点,直接插入结点
  node* np = malloc_node(name, desc);
  if (np == NULL) return 0;//分配结点没有成功,则直接返回
  np->next = hashtable[hashvalue];
  hashtable[hashvalue] = np;
  return 1;
}
/* A pretty useless but good debugging function,
which simply displays the hashtable in (key.value) pairs
*/
void displayHashTable()
{//显示hash表元素(不包括空)
  node *np;
  unsigned int hashvalue;
  for(int i=0; i < HASHSIZE; ++i)
  {
    if(hashtable[i] != NULL)
    {
      np = hashtable[i];
      printf("\nhashvalue: %d (", i);
      for(; np != NULL; np=np->next)
        printf(" (%s.%s) ", np->name, np->desc);
      printf(")\n");
    }
  }
}
void cleanUp()
{//清空hash表
  node *np,*tmp;
  for(int i=0;i < HASHSIZE; ++i)
  {
    if(hashtable[i] != NULL)
    {
      np = hashtable[i];
      while(np != NULL)
      {
        tmp = np->next;
        free(np->name);
        free(np->desc);
        free(np);
        np = tmp;
      }
    }
  }
}
int main()
{
  char* names[]={"First Name","Last Name","address","phone","k101","k110"};
  char* descs[]={"Kobe","Bryant","USA","26300788","Value1","Value2"};
  for(int i=0; i < 6; ++i)
    insert(names[i], descs[i]);
  printf("we should see %s\n",search("k110"));
  insert("phone","9433120451");//这里计算的hash是冲突的,为了测试冲突情况下的插入
   printf("we have %s and %s\n",search("k101"),search("phone"));
  displayHashTable();
  cleanUp();
  return 0;
}

输出结果:


需要特别注意一下,上面的hash函数计算为29的,用的就是单链表的头插法来解决冲突,不要复杂化了问题!

上面的代码是我自己实现的版本,后面我还会继续增加一些其他更好的版本,Waiting!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值