搜索之散列法建立简易字典

    昨天学习搜索中两种常见且简单的搜索方式---线性搜索和二分搜索,今天则学习了另一种比较重要且相对较复杂的搜索方式----散列法搜索

       要知道的是,散列法搜索不像昨天介绍的两种搜索方式,线性搜索和二分搜索中元素可以排列在任意位置,而散列法搜索则是根据各元素的值来确定存储位置,然后将位置保管在散列表中,从而实现数据的高速搜索。其中散列表是一种数据结构,能对包含关键字的数据集合高效地执行动态插入,搜索,删除操作。虽然链表也能完成同样操作,但搜索和删除的复杂度都高达O(n).

        散列表由能容纳m个元素的数组T,以及根据关键字决定数组下标的函数共同组成。就像上面所说的,元素的位置室友元素值决定的,这就比较像一个字典了。散列表大致可通过以下方法实现:

insert(data)
   T[h(data.key)]=data

search(data)
   return  T[h(data.key)];

        这里我们默认data.key是整数,当然,不是整数的情况下,我们也可以通过一些手段将其转化为整数用来作为下标。

       而h(k)是根据k值求数组下标的函数,称为散列函数。散列函数的值域为[0,m-1],其中m是数组T的长度,为满足这个要求,我们一般将散列函数定义为取余运算,保证输出值在0~m-1之间,比如:

                                          h(k)=k mod m

       但是这个函数也许大家也能很容易发现她的问题,那就是不同的key对应同一散列值的情况,导致不同的元素存储在数组同一位置。为了解决这种问题,我们有很多方法。开放地址法就是解决这种冲突的常用手段。

  这里我们使用的是双散列结构的开放地址法,即使用两个散列函数共同确定元素位置:

                                  H(k)=h(k,i)=(h1(k)+i*h2(k))mod m

      散列函数h(k,i)拥有关键字k和i两个参数。其中i是发生冲突后计算下一个散列值的次数。也就是说一开始添加元素时会调用h(k,0),如果发生冲突,就会依次调用h(k,1),h(k,2),h(k,3).......

双散列结构的开放地址法的实现方法如下:

int h1(key) {return key mod M;}
int h2(key) {return 1+(key mod (M-1));}   
int h(key,i) {return (h1(key)+i*h2(key)) mod m}

int insert(T,key)
{
    i=0;
  while(true)
    j=h(key,i)
    if(T[j]==Null)   //未发生冲突,直接插入并返回下标
      T[j]=key
      return j
    else      //发生冲突,i自增,开始下一次的插入
      i++;
}

int search(T,key)
{
    i=0;
  while(true)
    j=h(key,i)
    if(T[j]==key)   //找到元素,返回下标
      return j
    else if(T[j]==Null or i>=m)      //查找结束,未找到元素,返回空
  else i++           //查找未结束,继续向下一个位置查找

}

这里要理解为啥找到T[j]==null就直接返回空表示未查找到,因为虽然key虽然可能发生冲突存在多个可能的下标的位置上,但是存放的顺序也是固定,也就是说不可能跳过h(key,i)的位置直接先存放在h(key,i+1)的位置,所以在找打j=h(key,i)上元素为空,那么后面的h(key,i+1),h(key,i+2)位置肯定也不需要查找了,肯定是不存在的。

  还有个要注意的问题,由于发生冲突时,每次移动的位置是确定的,都是h2(k),那么就要保证数组的长度m与h2(k)必须是互质的,否则会不断循环的发生冲突,比如说对于有个值key,使得h1(key)=3,h2(key)=4,而数组的长度m=16,那么在发证冲的情况下,寻找的下标依次是3,7,11,(3+3*4) mod16=15,(3+4*4) mod16=3,7,11.....这样就会不断地在3 7 11 15位置上发生冲突,这样的话肯定是不可以的。所以为了保证不出现这种情况,我们可以特意让m为质数,然后取一个小于m的值作为h2(key),或者最简单的方法就是直接令h2(k)=1,这样使得发生冲突时依次查找后面的位置。

   简单介绍了这种方法后,做个小题目吧,就直接用书上的题目了---

题目:请实现一个能执行以下命令的简易"字典"。

1.insert str:向当前字典插入字符串str

2.find str:当前字典中包含str时输出yes,不包含时输出no

输入:第一行输入命令数n.随后n行按顺序驶入n个命令。命令格式如上。

输出:对于find命令输出yes或no.每个输出占一行。

限制: 输入的字符串仅仅由'A','C','G','T'四种字母构成。

         1<=字符串长度<=12

         n<=1000000

输入示例:                                                  输出示例:

6                                                                   yes

insert  AAA                                                   no

insert  AAC                                                   yes

find  AAA

find  CCC

insert CCC

find  CCC

散列法搜索的原理已经介绍了,下面直接附上源代码:
#include<stdio.h>
#include<string.h>
#define M 1046527
#define NIL (-1)
#define L 14

char H[M][L];

int getChar(char ch)
{
	switch(ch)
	{
		case 'A':return 1;
	    case 'B':return 2;
	    case 'C':return 3;
	    case 'D':return 4;
	    default:return -1;
	}
}
long long getKey(char str[])
{
	long long sum=0,p=1;
	for(int i=0;i<strlen(str);i++)
	{
		sum+=p*getChar(str[i]);
		p*=5;
	}
	return sum;
}
int h1(int key) {return key%M;}
int h2(int key) {return 1+(key%(M-1));}

int find(char str[])
{
	long long key,h;
	key=getKey(str);            //将字符串转换为数值
	for(int i=0;;i++)
	{
		h=(h1(key)+i*h2(key))%M;
		if(strcmp(H[h],str)==0) return 1;   //strcmp(str1,str2),若str1==str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。
		else if(strlen(H[h])==0) return 0;   //找到的位置上字符串为空,则找不到该字符串 
	} 
	return 0;
}
int insert(char str[])
{
	long long key,h;
	key=getKey(str);
	for(int i=0;;i++)
	{
		h=(h1(key)+i*h2(key))%M;
		if(strcmp(H[h],str)==0) return 1;          //在字典中已经有了该"单词",不进行插入
		else if(strlen(H[h])==0)
		{
			strcpy(H[h],str);
			return 0;                                 //不存在该"单词",则在首次为空的位置进行插入 
		} 
	}
	return 0;
}
int main()
{
	int n,h;
	char str[L],com[9];
	for(int i=0;i<M;i++) H[i][0]='\0';          //初始化散列表,令所有字符串为空 
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%s %s",com,str);
		if(com[0]=='i')
		{
			insert(str);
		}
		else{
			if(find(str))
		    	printf("yes\n");
			else{
		    	printf("no\n");
		   }
		}
	}
	return 0;
}

PS:本篇博客因为代码格式的问题修改了好几次,在这里和大家道个歉~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值