禁止碼迷,布布扣,豌豆代理,碼農教程,愛碼網等第三方爬蟲網站爬取!
ios
3 Aaa Bbb Ccc # Bbb Ccc Ddd # Aaa2 ccc Eee is at Ddd@Fff # 2 1 2 1 3
50.0% 33.3%
這個情景的實現能夠分爲 2 個部分,分別是按文件存儲單詞和比較兩個文件的類似度。首先來看第 1 部分,這部分的存儲方式很靈活,能夠像相似於存儲圖結構同樣,關注點能夠用鄰接表或鄰接矩陣,關注邊可使用邊集數組來存儲。這裏能夠關注文件來存儲,即根據文件把屬於該文件的單詞組織到一個結構上。也能夠關注單詞,即作一個單詞索引表,每一個單詞都標註其在哪一個文件中出現過。
可是不管是使用哪一種手法,都須要對輸入的字符串進行處理。如測試樣例所示,咱們拿到的字符串並非乾淨的結構,例如 「Ddd@Fff」 就須要切片爲 「Ddd」 和 「Fff」。在這裏能夠遍歷輸入的字符串,若是是字母就繼續遍歷,若是是其餘字符就暫停遍歷,而且把單詞部分拷貝出來,這裏使用 isalpha() 函數來判斷是不是字母是個好的選擇。同時這道題忽視大小寫,所以能夠在切片時統一把字母搞成大寫或小寫,可使用 tolower() 函數或 touppre() 函數實現。固然了,你用 string 類或字符數組處理均可以,string 集成處理字符串更爲方便。別忘了單個單詞長度小於 10,大於 2。雖然具體組織到的結構不一樣,可是這個操做是共有的,所以給出僞代碼。
接下來根據關注的內容不一樣,我給出 3 種實現方法。算法
這種手法就要求把單詞按照文件的歸屬,存儲到每一個文件的結構中,達到的效果是在一個結構中保存了屬於該文件的全部單詞。因爲這裏的文件是給定的序號,所以可使用哈希表來存儲,衝突處理使用直接定值法。而對於每一個單詞而言,可使用哈希鏈來作,不過這裏能夠用 STL 庫的 set 容器來存放。這裏須要解釋一下,若是使用動態的結構鏈表、list、vector 也是能夠,可是這裏會出現單個文件的重複單詞,這就須要對結構進行去除,而以上結構中 list 和
vector 的去重能力較弱。set 容器主打的特色就是去重,而且內部實現的結構是紅黑樹,這樣查找起來在內部的運行速度也很快。
接下來就是如何查找相同單詞的問題了,除了內部能夠借用紅黑樹帶來的效率,還有如何在 2 個文件之間創建聯繫。方法和咱們當時作「一元多項式的乘法與加法運算」的思想差很少,就是遍歷其中一個文件,而後拿這個文件的每一個單詞去和另外一個文件中查看看有沒有相同的單詞。此處能夠選擇單詞數較少的單詞爲基準,去另外一個結構中查找,可使用.size()方法輕鬆獲得一個文件的單詞數量。因爲涉及到 STL 容器的遍歷問題,咱們須要申請一個迭代器,並運做 set 本身的.find()方法。數組
#include <iostream> #include <string> #include <set> using namespace std; #define MAXSIZE 101 int main() { int count, fre; //文件數、查找次數 string str; //單次輸入的單詞 string a_word; //單個分片的單詞 //文件單詞表,使用 HASH 思想實現 int files_a, files_b; //待查找的文件編號 int number_same = 0, number_all = 0; //重複單詞數、合計單詞數 //set 容器迭代器,查找時用 cin >> count; for (int i = 1; i <= count; i++) { cin >> str; while (str != "#") { for (int j = 0; j <= str.size(); j++) { if (isalpha(str[j]) != 0) //判斷是不是字母 { if (a_word.size() < 10) //限制單詞長度上限 { a_word += tolower(str[j]); //把單個字母加到末尾 } } else //遇到符號,分片單詞 { if (a_word.size() > 2) //限制單詞下限 { //將單詞插入對於文件的 set 容器中 } a_word.clear(); //清空字符串 } } cin >> str; } } cin >> fre; for (int i = 0; i < fre; i++) { cin >> files_a >> files_b; if (files[files_a].size() > files[files_b].size()) //選擇單詞數較小的文件爲基準 { count = files_a; files_a = files_b; files_b = count; } number_all = files[files_a].size() + files[files_b].size(); number_same = 0; for ( ) { //遍歷其中一個文件 if () //找到重複單詞 { number_same++; number_all--; } } printf("%.1f%%\n", 100.0 * number_same / number_all); } return 0; }
這種手法就要求把文件按照單詞的歸屬,存儲到每一個單詞的結構中,達到的效果是在一個結構中保存了含有該單詞的全部文件。因爲這裏的文件是給定的序號,所以標記該單詞出如今哪一個文件中,可使用哈希表來存儲,衝突處理使用直接定值法,經過這種手法能夠直接肯定單詞的出現位置。而對於每一個單詞而言,可使用哈希鏈來作,不過這裏能夠用 STL 庫的 map 容器來存放。這裏須要解釋一下,所謂單詞索引就是經過單詞直接找到它出如今那些文件,map 容器主打的特色就是構建一個映射,而且內部實現的結構是紅黑樹,這樣查找起來在內部的運行速度也很快。而這時就能夠以單詞自己做爲 key,而 value 就鏈接到一個起到 HASH 做用的數組,在這裏用數組綽綽有餘。
接下來就是如何查找相同單詞的問題了,這裏就會遇到一個小問題,就是文件中有哪些單詞是徹底未知的。解決方法是直接用迭代器遍歷 map 容器,對於每一個單詞都進行檢查,若該單詞同時出如今 2 個文件中,就修正重複單詞數和重複單詞數,若進出如今一個文件就只修正重複單詞數。這裏單詞的規模會對效率進行限制,不過肯定單詞存在於那些文件的速度是很快的,能夠用下標直接訪問數組。函數
#include <iostream> #include <string> #include <map> using namespace std; int main() { int count, fre; //文件數、查找次數 string str; //單次輸入的單詞 string a_word; //單個分片的單詞 //單詞索引表 int files_a, files_b; //待查找的文件編號 int number_same = 0, number_all = 0; //重複單詞數、合計單詞數 //map 容器迭代器,遍歷時用 cin >> count; for (int i = 1; i <= count; i++) { cin >> str; while (str != "#") { for (int j = 0; j <= str.size(); j++) { if (isalpha(str[j]) != 0) //判斷是不是字母 { if (a_word.size() < 10) //限制單詞長度上限 { a_word += tolower(str[j]); //把單個字母加到末尾 } } else //遇到符號,分片單詞 { if (a_word.size() > 2) //限制單詞下限 { //對單詞構建映射 } a_word.clear(); //清空字符串 } } } } cin >> fre; for (int i = 0; i < fre; i++) { cin >> files_a >> files_b; number_all = number_same = 0; for () { //遍歷 Index_table 中全部單詞 if () { //單詞在 2 個文件中都出現 number_same++; number_all++; } else if() { //單詞出如今 2 個文件其中之一 number_all++; } } printf("%.1f%%\n", 100.0 * number_same / number_all); } return 0; }
既然我能夠用 2 種不一樣的視角去構建結構,爲何不一樣時用起來呢?同時構建的方法也很簡單,就是在分片單詞的時候同時作就行。這麼作能夠同時繼承以上 2 種手法的特色,即遍歷其中一個文件的單詞,可是這時無需去另外一個文件查找,而是直接去單詞索引表查看是否在另外一個文件中出現,所以選擇單詞數更小的文件來遍歷效率更高。
不過,若是是僅僅這個情景,效率其實並無第一種手法快,由於維護 2 種容器的內部開銷不可避免。可是若是跳出這個情景,把它當成一個應用程序來看,這種手法無疑有更加的健壯性。由於我同時擁有 2 種不一樣信息的表,這爲我添加更多的功能提供了基礎。例如能夠對多個文件進行整體的詞頻統計,這個就能夠利用單詞索引表實現,而僅僅有文件單詞表就須要把全部表所有都遍歷一遍,那效率就過低了!測試
#include <iostream> #include <string> #include <set> #include <map> using namespace std; int main() { int count, fre; //文件數、查找次數 string str; //單次輸入的單詞 string a_word; //單個分片的單詞 //單詞索引表 int files_a, files_b; //待查找的文件編號 int number_same = 0, number_all = 0; //重複單詞數、合計單詞數 //set 容器迭代器,查找時用 //文件單詞表,使用 HASH 思想實現 cin >> count; for (int i = 1; i <= count; i++) { cin >> str; while (str != "#") { for (int j = 0; j <= str.size(); j++) { if (isalpha(str[j]) != 0) //判斷是不是字母 { if (a_word.size() < 10) //限制單詞長度上限 { a_word += tolower(str[j]); //把單個字母加到末尾 } } else //遇到符號,分片單詞 { if (a_word.size() > 2) //限制單詞下限 { Index_table[a_word][i] = 1; //將單詞插入對於文件的 set 容器中 //對單詞構建映射 } a_word.clear(); //清空字符串 } } cin >> str; } } cin >> fre; for (int i = 0; i < fre; i++) { cin >> files_a >> files_b; if (files[files_a].size() > files[files_b].size()) //選擇單詞數較小的文件爲基準 { count = files_a; files_a = files_b; files_b = count; } number_all = files[files_a].size() + files[files_b].size(); number_same = 0; for () { //遍歷其中一個文件 if () //找到重複單詞 { number_same++; number_all--; } } printf("%.1f%%\n", 100.0 * number_same / number_all); } return 0; }
調試中主要遇到了 2 個技術性問題:
Q1:手法一中,vector 引起的去重問題。
A1:因爲一開始沒有考慮一個文件中重複單詞的問題,所以選擇了 vector 來動態存儲單詞,這時有重複單詞就會被屢次計數,那就會使答案出錯。若是用泛型算法 find() 來處理的話,那就每次添加單詞都要搞一遍,效率過低了,必超時,並且用 find() 來肯定單詞的存在也會超時。最後將 vector 通通改爲 set 容器,直接實現去重功能解決這個問題。
Q2:手法二中,vector 引起的哈希表查找問題。
A2:本來我 map 容器內的值是一個 vector 容器,因爲 vector 是動態往裏面添加空間,所以獲得的是哈希鏈而不是哈希表。這就說明每次查找仍是要用 find() 函數,這就形成了超時問題,所以就要對容器提早動態分配一些空間。不過這個用數組來作就綽綽有餘了,所以改爲數組,迭代器的類型相應地修改就好。
還遇到了 1 個非技術性問題:
Q3:出現了內存超限問題;
A3:用結構體或容器做爲 map 的值的用法我好久沒寫了,所以忘記了不須要另外申請空間,每次構建映射時我都申請一個很大的數組。最後調試時,我忘記刪掉了這個操做,這就致使了每處理一個單詞,就要申請一大堆空間,這樣空間就被快速地消耗了。刪除這個操做就能夠解決內存超限的問題,這仍是我第一次遇到。網站
上面已經說得夠明白了,不須要再多說一遍。spa