零零散散學算法之判斷集合的相同&相似性

深入解析集合的相同與相似性

正文

       所謂集合的相似性就是說,對於集合A和集合B(當然,這兩個集合是隨意的),我們可通過某種方式來比較這兩個集合是否具有一些相同的特徵和聯繫,並從中得知這二者相似的百分比有多大。


       當然,相似性也包括了相同,這就好比正方形屬於矩形一樣。如果兩個集合相同的話,那麼他們之間相似的百分比就是100了。


       本文將分別解析集合相同與相似這兩部分,尤其是判斷相同,我將會用代碼和圖例來說明。


第一節 判斷集合是否相同


       判定兩個集合是否相同,舉個例子:現有串A = “總書記 軍委主席 國家主席”,和串B = “國家主席 軍委主席 總書記”,我們將串A和串B看做集合A和集合B,從兩個集合的內容我們看到它們是相同的,只是順序不同而已。(附註:對於很短的詞串,我們可以自己通過統計來做判斷,但是集合中如果有大量的詞串,人工統計的方法顯然是不可取的)。

       那我們能通過哪些方法來判定集合是否相同呢?通過複雜度的改進,我將介紹四種方法。


       方法一:逐個比較法

       這種方法太容易想到了:假設集合A和集合B中的元素個數均爲N,那麼只需對集合中的元素一一作比較,這樣的話複雜度爲O(N * N),即用兩個for語句就可以搞定了!這種解法可以說是最笨的算法了。代碼濾過!


       方法二:排序法

       首先,對兩集合先分別做排序,然後在進行比較。做排序可以用快速排序,複雜度爲O(N * LogN),作比較需O(N),綜合下來O(N * LogN) + O(N) ---> O(N * LogN)。顯然,這比方法一提高了一些。不過這離我們想要達到的效果還是有距離的!


       說到這,額外說幾句。在最近的寫的幾篇博文中都涉及到了排序算法(不過很慚愧,每篇博客發表的週期大概都在一個月左右),而涉及到排序算法的時候,我們一般都會首選快速排序,其次是堆排序和歸併了。那麼我在這套用JULY的一句話:“你能在三十分鐘內很清晰的講解出快速排序算法,並很準確的寫出來嗎”?很多人說這沒有意義,背下來不就寫出來了,但是別忘了,還要求你清晰的講解它。這就要求我們不僅能寫出來,而且更要理解它、掌握它。這不禁使我想起前輩吳軍在《數學之美》裏說的:技術分爲道和術,做事的方法是術,做事的原理和原則是道!我們在學習的過程中不僅要學會“術”,更要慢慢掌握“道”。


       方法三:哈希映射

       關於哈希方面的知識我就不羅嗦了!首先,我們將其中任意一個集合映射到一張哈希表中,當然這張哈希表的大小爲O(N)。然後將另一個集合的元素一一與哈希表中的元素作比較,如果全部匹配,那麼就說明這兩個集合時相同的。此時,我們分析一下,創建一張哈希表需O(N)的空間,作比較需O(N)的時間,感覺哈希算法還是很不錯的,都在線性的範圍內解決。唯一讓我們覺得遺憾的是需要O(N)的空間,那麼有沒有辦法在不佔用空間的前提下,在線性時間內判斷集合相同的問題呢?答案是肯定的!


給出法三的代碼之前,給個圖例:

char * HashMapping(char *Set, char *HashTable, int Set_Size)
{
/***	Set:爲將要產生指紋的集合
 ***	HashTable: 映射產生的hash表
 ***	Set_Size: 爲集合的大小
***/

	for(int i = 0; i < Set_Size; i++)
	{
		HashTable <-- hash(Set[i]);
	}

	return HashTable;
}
bool HashCompared(char *HashTable, char *ComparedSet, int Set_Size)
{
/***	HashTable爲由集合已經產生的hash表
 ***	ComparedSet爲比較的集合
***/

	for(int i = 0; i < Set_Size; i++)
	{
		if(HashTable(hash(Set_B[i])) == Set_B[i])
		{
			continue;
		}
		else
		{
			return false;
		}
	}
	return true;
}

       方法四:指紋識別法

       我們知道,人的指紋幾乎是唯一的,發生相同的概率是及其微小的。於是我們可將此種思想引入到判定集合是否相同的問題。具體的想法是:如果兩個集合是相同的,那麼這兩個集合所產生的指紋必定是相同的,即使集合裏子串的順序是不相同的,也沒關係(根據交換律)。


       我們定義一集合S = {e1, e2, e3, … , en}的指紋FP(S) = FP(e1) + FP(e2) + … + FP(en),其中FP(e1)FP(e2), … , FP(en)分別爲S中這些元素對應的指紋。於是,不同的集合所產生的指紋就不一樣,相同的集合所長生的指紋必定一致。當然,也會出現不同的集合產生相同指紋的情況,但是這種情況發生的概率是非常小的,每一千八百億億次纔出現一次,我們可認爲發生的概率爲0


       通過介紹指紋識別算法,我們看到它的複雜度爲O(N),而且不需要額外的空間,我們很滿意這樣的結果。


同法三一樣,給出代碼之前,給個圖例:

char * FingerPrint(char *Set, char *FP, int Set_Size)
{
/***	Set:爲將要產生指紋的集合
***	FP:爲將要產生的指紋
***	Set_Num:爲集合的大小
***/

	for(int i = 0; i < Set_Num; i++)
	{
		FP[i] = handle(Set[i]);
		FP += FP[i];
	}
	
	return FP;
}
bool CompareSet(char *FP_A, char *FP_B)
{
	return FP_A && FP_B ? TRUE:FALSE;
}

第二節 判斷集合相似性


       說起相似性,我們可能會立刻想到第一節講的相同,的確,相似和相同的關係就好比正方形和矩形的關係一樣。其實,相似的情況在學習和生活中很常見,比如,我們中學時學的相似三角形;一對雙胞胎長的有多像;兩篇文章中相同內容的百分比有多大等等。這些都是相似性的問題。


       那我們是怎樣來判斷兩個集合之間的相似性呢?我們還是利用相同的思想,只不過是如果兩個集合相同的話,它們之間的相似性爲百分之百罷了;如果不相同的話,只是介於百分之零到百分之一百之間(前閉後開區間,即[0100))。

       Ok,我們來看看解決的辦法。


       方法一:逐個比較法(同第一節法一)

       對兩個集合中的每個子串進行一次徹頭徹尾的比較。


       方法二:特徵詞提取法

       對兩個集合徹頭徹尾的比較,如果兩篇文章篇幅都很大,這顯然是很慢的。於是就有了特徵詞提取法。該方法的思想是:在集合中提取與主題中心思想有關聯的一些特徵詞,之後對這些特徵詞進行比較。如果這些特徵詞相似度達到80%或者90%或者其它,我們就說這兩篇文章是相似的。

       因此,這種方法的關鍵就在特徵詞的選取了。前面已經說過,一定要選擇和主題聯繫大的詞。我們可以用TF-IDF算法來提取集合中的特徵詞,而IDF越大的詞鑑別能力越強,所以我們只需找到IDF最大的一些詞,來比較它們的相似度,進而就能得到兩個集合是否相似。


       方法三:分治法 特徵詞提取法

       該方法相比於法二增加了分治的思想,即我們可將一個大的集合分成一些小的集合,然後分別對這些小的集合進行特徵詞提取法,完成之後,再對這些小集合進行比較就能判斷這兩個大集合是否相似。利用這種方法,我們就能判斷文章之間的抄襲問題。


       好了,以上就是解決集合相似度的幾種方法。在相似度這節,我沒有給出相關的代碼,主要還是想學習算法的思想,再加上相同和相似在某些方面是一致的,第二節講的也比較詳細,所以不再贅述。


第三節 結束語


       想想、寫寫、畫畫......


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