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

    昨天學習搜索中兩種常見且簡單的搜索方式---線性搜索和二分搜索,今天則學習了另一種比較重要且相對較複雜的搜索方式----散列法搜索

       要知道的是,散列法搜索不像昨天介紹的兩種搜索方式,線性搜索和二分搜索中元素可以排列在任意位置,而散列法搜索則是根據各元素的值來確定存儲位置,然後將位置保管在散列表中,從而實現數據的高速搜索。其中散列表是一種數據結構,能對包含關鍵字的數據集合高效地執行動態插入,搜索,刪除操作。雖然鏈表也能完成同樣操作,但搜索和刪除的複雜度都高達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:本篇博客因爲代碼格式的問題修改了好幾次,在這裏和大家道個歉~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章