字符串Hash

Hash,我們又稱散列,指的是我們通過一個散列算法,把輸入值變換成另一個輸出值,通常來說,是變得更易於我們處理的一個值,輸出值的值域通常小於輸入值的值域,這個過程也叫映射。

在之前的學習中,我用的比較多的主要還是整數的映射,今天碰到一道字符串的題目,所以嘗試了一下字符串映射的操作,特此記錄。

題目:

輸入一個N和M,其中中間以空格隔開,接下來是N個由三個小字字母組成的字符串和M個同樣格式的字符串
每個字符串佔一行,輸出M個字符串的內容以及分別在前面N個字符串中出現的次數,中間以空格隔開,每個
輸出佔一行。
示例輸入:
6 2
doc
txt
exe
doc
txt
txt
doc
txt
示例輸出:
doc 1
txt 3

首先我們來想想,用一般的方法如何做?
既然需要查詢M個字符串在前N個字符串中出現了多少次,那麼我們直接雙重循環即可實現,拿每一個字符串分別與前N個做比較,如果相同則累加次數,最終輸出即可。不難算出,此算法的時間複雜度爲O(N*M),當數據量小的時候,這也不失爲一種好辦法,畢竟簡單易懂,但是如果數據量上來了,M和N達到了104 或者更多的時候,我想這可能就行不太通了。

我們分析一下這裏的時間複雜度主要是什麼操作貢獻的:首先M個字符串要輸出,那麼外層循環M肯定少不了的(我們暫且不看輸入的哈~),那麼內層的N是不是有這個必要呢?我們找到了一個“doc”,累加了1;然後找到了一個“txt”,忽略它;找到“exe”,忽略;找到“doc”,累加…然後我們來找txt,找到“doc”,忽略;找到“txt”,累加…然後我們得出結論“txt”出現了3次。我們在找doc的時候,並沒有爲我們找txt提供任何幫助,導致我們在找txt又從第一個開始找,M個字符串,每一個都需要從第一個開始找,查詢複雜度爲O(N),最終時間複雜度爲O(N*M)。
那我們有辦法避免嗎?有!既然外層M不能少,而內層主要是查詢導致複雜度偏高的,那我們用Hash空間換時間降低查詢的複雜度,查詢的過程直接像查字典一樣複雜度爲O(1).

我們對數字用Hash函數的時候,通常是採取直接尋址法、除留餘數法等等,而字符串怎麼處理呢?字符串每個位的字符都是26個字母,有多個字符,當字符位數不夠多時,我們可以把它當成一個26進制數字處理,而一個其它進制的數轉成10進制是唯一的,且10進制可以直接作爲下標(類似直接尋址法).
我們來計算一下3位的字符串需要的Hash表要多長:

((26*0+25)*26+25)*26+25 = 25*26*27+25 = 17575

emmmmm…勉強在接受範圍內吧,好啦下面奉上本題採用字符串Hash的代碼:

#include<stdio.h>
#define MAX 100
char str[MAX][4];
int table[25*26*27+26] = {0};//化成26進制處理   假設((26*0+25)*26+25)*26+25 = 25*26*27+25   
int hash(char *s,int len);
int main(void)
{
	int n,m;
	char s[4];
	//freopen("data.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i = 0;i < n;++i)
	{
		scanf("%s",s);//原始的n個不需要儲存,只需要計算映射後的值並累加即可。 
		++table[hash(s,3)];
	}
	for(int i = 0;i < m;++i)
		scanf("%s",str[i]);
	for(int i = 0;i < m;++i)
	{
		printf("%s %d",str[i],table[hash(str[i],3)]);
		if(i != m-1)
			putchar('\n');
	}  
	return 0;
}
int hash(char *s,int len)
{
	int num = 0;
	for(int i = 0;i < len;++i)
	{
		num = num*26 + (s[i] - 'a');//把對應的字符當成26進制數字來處理,化成10進制 
	}
	return num; 
} 

接下來是樸素的代碼:

#include<stdio.h>
#include<string.h>
#define MAX 200
char str[MAX][4];
int main(void)
{
	int n,m;
	char s[4];
	//freopen("data.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i = 0;i < n;++i)
		scanf("%s",str[i]);
	for(int i = 0;i < m;++i)
		scanf("%s",str[n+i]);
	for(int i = n;i < n+m;++i)
	{
		int count = 0;
		for(int j = 0;j < n;++j)
		{
			if(strcmp(str[i],str[j]) == 0)
				++count;
		}
		printf("%s %d",str[i],count);
		if(i != m-1)
			putchar('\n');
	}  
	return 0;
}

我用文件重定向方式去掉了輸入的時間,分別看了一下兩種方法的時間:
在這裏插入圖片描述
在這裏插入圖片描述
上面的是使用了Hash算法的,下面的是採用了樸素查找算法的,可以發現使用Hash算法速度略快,在字符串較多的時候效果應該更明顯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章