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

    xiaoxiao2021-03-26  19

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

           要知道的是,散列法搜索不像昨天介绍的两种搜索方式,线性搜索和二分搜索中元素可以排列在任意位置,而散列法搜索则是根据各元素的值来确定存储位置,然后将位置保管在散列表中,从而实现数据的高速搜索。其中散列表是一种数据结构,能对包含关键字的数据集合高效地执行动态插入,搜索,删除操作。虽然链表也能完成同样操作,但搜索和删除的复杂度都高达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:本篇博客因为代码格式的问题修改了好几次,在这里和大家道个歉~

    转载请注明原文地址: https://ju.6miu.com/read-659968.html

    最新回复(0)