哈希表的原理和实现

数据结构:hash_map原理 
这是一节让你深入理解hash_map的介绍,如果你只是想囫囵吞枣,不想理解其原理,你倒是可以略过这一节,但我还是建议你看看,多了解一些没有坏处。

hash_map基于hash table(哈希表)。哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。

其基本原理是:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。

但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。其插入过程是:

1. 得到key
2. 通过hash函数得到hash值
3. 得到桶号(一般都为hash值对桶数求模)
4. 存放key和value在桶内。

其取值过程是:

1. 得到key
2. 通过hash函数得到hash值
3. 得到桶号(一般都为hash值对桶数求模)
4. 比较桶的内部元素是否与key相等,若都不相等,则没有找到。
5. 取出相等的记录的value。

hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。这里可以看出,如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候).

由此可见,要实现哈希表, 和用户相关的是:hash函数和比较函数。这两个参数刚好是我们在使用hash_map时需要指定的参数。

来源:  http://www.stlchina.org/twiki/bin/view.pl/Main/STLDetailHashMap

 


一个实现

 

 

 

#include <math.h>

#include <stdio.h>

#include <stdlib.h>

#define MAXSIZE 30

typedef enum{False,True}  BOOL;

typedef enum{NULLKEY,HAVEKEY,DELKEY} HAVEORNOT;

 

typedef struct

{

int elem[MAXSIZE];

HAVEORNOT elemflag[MAXSIZE];

int count;

}HashTable;

typedef struct

{int keynum;

}Record;

void InitialHash(HashTable*);

void CreateHash(HashTable*);

void PrintHash(HashTable);

BOOL SearchHash(HashTable,int,int*);

BOOL InsertHash(HashTable*,Record);

BOOL DeleteHash(HashTable*,Record);

int Hash(int);

 

void main()

{

       HashTable H;

       char ch,j='y';

       int position;

       Record R;

       BOOL temp;

       //textbackground(3);

       //textcolor(15);

       //clrscr();

       InitialHash(&H);

       CreateHash(&H);

       

       printf("This program will show how to operate to a HashTable.\n");

       printf("You can display all elems,search a elem,\ninsert a elem,delete a elem.\n");

       

 

       while(j!='n')

       {

              printf("1.display\n");

              printf("2.search\n");

              printf("3.insert\n");

              printf("4.delete\n");

              printf("5.exit\n");

              scanf(" %c",&ch);

              switch(ch)

              {

              case '1':if(H.count) PrintHash(H);

                     else printf("The HashTable has no elem!\n");

                     break;

              case '2':if(!H.count) printf("The HashTable has no elem!\n");

                     else

                     {printf("Please input the keynum(int) of the elem to search:");

                     scanf("%d",&R.keynum);

                     temp=SearchHash(H,R.keynum,&position);

                     

                     if(temp) printf("The position of the elem is %d\n",position);

                     else printf("The elem isn't exist!\n");

                     }

                     break;

              case '3':if(H.count==MAXSIZE)

                     {printf("The HashTable is full!\n");

                     break;

                     }

                     printf("Please input the elem(int) to insert:");

                     scanf("%d",&R.keynum);

                     temp=InsertHash(&H,R);

                     

                     if(temp) printf("Sucess to insert the elem!\n");

                     else printf("Fail to insert the elem.The same elem has been exist!\n");

                     break;

              case '4':printf("Please input the keynum of the elem(int) to delet:");

                     scanf("%d",&R.keynum);

                     temp=DeleteHash(&H,R);

                     

                     if(temp) printf("Sucess to delete the elem!\n");

                     else printf("The elem isn't exist in the HashTable!\n");

                     break;

              default: j='n';

              }

       }

       printf("The program is over!\nPress any key to shut off the window!\n");

       getchar();

}

 

void InitialHash(HashTable *H)

{

       int i;

       (*H).count=0;

       for(i=0;i<MAXSIZE;i++) (*H).elemflag[i]=NULLKEY;

}

 

void CreateHash(HashTable *H)

{

 Record e;

 printf("请输入的一系列整数(不超过12个,以-1结束)以建立哈希表:\n");

 scanf("%d",&e.keynum);

 while(e.keynum!=-1)

    if(InsertHash(H,e))  scanf("%d",&e.keynum);

    else

      {printf("请输入不重复的数据!");

       return;

          

}

 

void PrintHash(HashTable H)

    

       int i;

       for(i=0;i<MAXSIZE;i++)

       if(H.elemflag[i]==HAVEKEY)

       printf("%-4d",i);

       printf("\n");

       for(i=0;i<MAXSIZE;i++)

       if(H.elemflag[i]==HAVEKEY)

       printf("%-4d",H.elem[i]);

       printf("\ncount:%d\n",H.count);

}

 

BOOL SearchHash(HashTable H,int k,int *p)

{

       int p1;

       p1=(*p)=Hash(k);

       while(H.elemflag[(*p)]==HAVEKEY&&k!=H.elem[(*p)])

       

       {

              (*p)++;

              if((*p)>=MAXSIZE) (*p)=(*p)%MAXSIZE;

              if((*p)==p1) return False;

       }

       if(k==H.elem[(*p)]&&H.elemflag[(*p)]==HAVEKEY)

              return True;

       else return False;

}

 

BOOL InsertHash(HashTable *H,Record e)

{

       int p;

       if(SearchHash((*H),e.keynum,&p))

       return False;

       else

       {(*H).elemflag[p]=HAVEKEY;

       (*H).elem[p]=e.keynum;

       (*H).count++;

       return True;

       }

}

 

BOOL DeleteHash(HashTable *H,Record e)

{

       int p;

       if(!SearchHash((*H),e.keynum,&p))

       return False;

       else

       {(*H).elemflag[p]=DELKEY;

       (*H).count--;

       return True;

       }

}

 

int Hash(int kn)

{

       return (kn);


 


哈希表

 

 


基本概念
  * 若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。 
  * 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象” 作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。 
  * 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。 
编辑本段常用的构造散列函数的方法
  散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位: 
  1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做自身函数) 
  2. 数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。 
  3. 平方取中法:取关键字平方后的中间几位作为散列地址。 
  4. 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。 
  5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。 
  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。 
编辑本段处理冲突的方法
  1. 开放寻址法:Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法: 
  1. di=1,2,3,…, m-1,称线性探测再散列; 
  2. di=1^2, -1^2, 2^2,-2^2, (3)^2, …, ±(k)^2,(k<=m/2)称二次探测再散列; 
  3. di=伪随机数序列,称伪随机探测再散列。 == 
  2. 再散列法:Hi=RHi(key), i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。 
  3. 链地址法(拉链法) 
  4. 建立一个公共溢出区 
编辑本段查找的性能分析
  散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。 
  查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素: 
  1. 散列函数是否均匀; 
  2. 处理冲突的方法; 
  3. 散列表的装填因子。 
  散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度 
  α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。 
  实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值