演算法之個人總結 Hash表之簡單應用

2021-05-23 11:05:46 字數 3667 閱讀 5346

**:http://chelu01.blog.163.com/blog/static/9417780520102512312223/(本人感覺相當不錯的hash)

前段時間看了個微軟編寫的c庫函式,在這個庫函式裡學到乙個自我感覺相當牛比的小演算法,說白了是hash表的應用。大家都知道,hash表最主要是用來實 現查詢功能的,再具體點是用常量級時間複雜度找到你想查詢的東西。首先,我以乙個小問題引入將要介紹的hash演算法,問題如下:

現有字串str1,str2,編寫乙個函式返回str1中有多少個字元在str2字串中。

其實這個函式也很簡單(假如我們不考慮時間複雜度的話),遍歷str1字串,其中對於str1中的每乙個字元都要去判斷是否在str2字串中,如果在統計變數加1,即需要兩個for迴圈,即時間複雜度為0(lena*lenb),程式也很簡單,如下:

//返回字串str1中有多少個字元在str2字串中

int charcount(const char* str1, const char* str2)}}

return ct;

}這 個程式每次判斷str1中的字元是否在字串str2中時,str2字串均需要從開頭遍歷,即每次需要回溯,從而使得整個演算法的時間複雜度很高,那麼我 們是否可以用常量級時間複雜度來執行上面程式的內迴圈?即我們是否可以用常量級時間複雜度來判斷某字元是否在字串str2中?答案是肯定的,此時就需要 hash表。

首先我先敘述下關於這個思路微軟的演算法是什麼。即如何用常量級時間複雜度來判斷乙個字元是否在某個字串中。首先我們先解決這個問 題,你能否按照某種對映方式將256個字元一一對映到乙個陣列裡,換句話說已知char arr[32],你能否將256個字元對映到這個陣列裡?

我 們都知道ascii值總共是256個(即2^8),如果我們將這256個字元分組,每組字元個數是8,那麼可以分256/8組,即32組,因此對於 ascii值為c的字元,它屬於編號為c / 8的 組(組的編號是0-31),因此,我們可以建立乙個含有32個元素的陣列,編號相同的8個字元存放在 乙個單元,在乙個單元裡如何來區分這8個字元呢?如果接著分析下去,易知,每一組中的8個字元除以8的餘數均是0-7,因此,我們可以根據這8個字元除以 8的餘數來分別區分它們,具體辦法如下:用乙個char型別資料,char型別資料的最低位來表示除以8餘數是0的那個字元,倒數第二位表示除以8餘數是 1的字元,從而可以將每一組中的8個字元用乙個char型別資料的每一位表示。例如:ascii值是49的字元應該這樣存放在hash表中(含有32個元 素的陣列),首先49/8等於6 即編號是6,其次49%8等於1,即這個單元的char資料的bit1位是1,即arr[6]中的char資料的二進位制 應該為 00000010;

有了上述演算法的思想,那麼我們就可以用char arr[32]來存放乙個字串str2了,依次遍歷這個字串,按 照上述原則依次標記陣列中相應位置,字串str2中每乙個字元c的位置是arr[ c/8],其次使得該char資料的第c%8 bit 置1,假如 arr陣列中每乙個元素初始值均為0,那麼可以這樣實現:

//使得編號為c/8的單元中的char資料的第 c%8 bit 置1

arr[ c/8 ]   =  arr[ c/8 ]   |   ( 1 << (c % 8) );

如果將str2中的每乙個字元按照上述原則標記好了,假如我們要在str2中查詢字元a,那麼可以判斷編號為a/8的單元中的char資料的第 a%8 bit是否為1,如果是說明字元a在str2中,即:

if  (   (arr[a / 8]  &  ( 1  <<  (a % 8)  ) )  != 0 )

//說明字元a存在的

看看利用這種方法是不是實現了常量時間複雜度查詢某字元是否在字串中?嘿嘿!

重新回到開頭的那個問題,判斷str1中的字元是否在str2中,查詢演算法就可以用上述思路來實現,即只需要遍歷str1字串,之後用上述常量時間複雜度的查詢演算法判斷str1當前字元是否在str2中,易知時間複雜度是o(lena),演算法如下:

int charcount_(const char* str1, const char* str2)

int ct = 0;

//遍歷str1字串,判斷每個字元是否在hash表中(是否在str2字串中)

for (; *str1 != '/0'; ++str1)

return ct;

}其 實上述演算法還可以加快,我們都知道計算機在處理除法以及取模運算時相對是較慢的,因此我們可以用位運算來實現上面的 除法和取模運 算,*str1 / 8 等價於 *str1 >> 3,另外,乙個數n除以 2^m 的餘數 實際上是n的低 m bit位所表示的十進位制 數,即*str1%(2^3)等價於 *str1 & 7(具體可以自己想),因此上述演算法的極限**是:

int charcount_(const char* str1, const char* str2)

int ct = 0;

//遍歷str1字串,判斷每個字元是否在hash表中(是否在str2字串中)

for (; *str1 != '/0'; ++str1)

return ct;

}至此,整個演算法已經詳細給出。

總結:1) 看到此不知道你是否徹底理解了上述hash表的建立,其實細心的讀者可能會提出這樣的問題,為什麼把256個字元分成32組(每組只存放8個字元)?呵 呵,其實不一定非得分成32組,也可以分成16組,也可以分成8組,。。。其實演算法的本質是在記憶體中用256bits來表示這256個字元是否存在,我們 完全可以分成16組,每一組用16bit來表示,即 short arr[16];或者 分成8組,即int arr[8],自己可以實現下。

2)現在你能否實現乙個這樣的函式:

size_t strspn (const char *s,const char * accept);

函 數說明:strspn()從引數s 字串的開頭計算連續的字元,而這些字元都完全是accept 所指字串中的字元。簡單的說,若strspn()返 回的數值為n,則代表字串s 開頭連續有n 個字元都是屬於字串accept內的字元,即字串s中第n+1個字元不屬於accept字串中,要求 時間複雜度為0(n)(嘿嘿,其實這就是乙個c庫函式,相信你時間複雜度為0(n*n)的演算法馬上就能寫出來吧)

3)回顧文章開始那個問題,其實是利用了空間換取時間的思想,即增加了演算法的空間複雜度(因為我們額外建立了乙個hash陣列),但是演算法的時間複雜度變成了o(n),因此在記憶體空間不是問題的情況下,利用這種增加空間複雜度換取時間複雜度的思想值得我們去考慮。

4) 如果你足夠細心,那麼利用本篇介紹的hash演算法,你此時一定能將c庫函式strtok實現出來,個人認為這個庫函式是字串庫函式中最最難的乙個,不僅 其功能難理解,實現起來也很困難,呵呵,其實我就是在看微軟編寫的strtok原始碼的時候才發現了本篇這個hash演算法的,哈哈,strtok這個函式一 定要去看哦,諾西兩年筆試題目均從不同角度考察了該函式的實現。

總結2)strspn函式**補充:

//演算法時間複雜度為o(len1*len2)

int mystrspn(const char* str1, const char* str2)

if (*temp == '/0')  //如果遍歷完str2了(說明*str1不在str2中)

break;

else

++ct;

}return ct;

}//時間複雜度為o(len1)

int mystrspn_(const char* str1, const char* str2)

return ct;

}以上只是個人看法,個人理解,如有不足請指正!3q!

《演算法導論》之hash表

hash表原理不難,難在如何選擇表的大小以及hash函式,這個涉及到很多數學的東西,這裡就先不詳述了,而是先整理下基本概念和一些結論。hash就是通過函式,將大範圍的key值對映到小範圍的hash表中,相比起直接對映來說更加節省空間。而這種方法的乙個問題就是會出現 碰撞 即有多個值會對映到同乙個 項...

資料結構之hash表

1.查詢的資料是表式結構,可以想象成是資料庫裡的表 2.查詢的目的是找出表中關鍵字與給定關鍵字相等的資料行 3.雜湊表就是定義了每乙個資料行的行號 4.雜湊演算法就是 行號 f 關鍵字 所以在雜湊表裡查詢資料時,不需要把給定關鍵字與每一行的關鍵字比較是否相等來找到行號,而是直接由給定的關鍵字計算出行...

Struts 原理 之個人總結

控制器元件由actionservlet 和自定義action組成 那麼actionservlet在 呢?我做完了乙個struts的先demo 也沒看見啊 只有自定義的action 第一章中的自定義框架還是有的了 其實仔細想想不難發現,自定義框架中的操作已經全部被struts config.xml 配...