《演算法》筆記 14 單詞查詢樹

2022-06-05 17:06:08 字數 3755 閱讀 8189

三向單詞查詢樹

同字串的排序一樣,利用字串的性質開發的查詢演算法也比通用的演算法更有效,這些演算法可以用於在以字串作為被查詢鍵的場合。這類演算法在面對巨量的資料時,仍然可以取得這樣的效能:查詢命中所需的時間與被查詢的鍵的長度成正比;而查詢未命中時只需檢查若干個字元。這樣的效能是相當驚人的,也是演算法研究的最高成就之一,這些演算法成了建成現在能夠便捷、快速地訪問海量資訊所依賴的基礎設施的重要因素。

資料結構

private static class node
每個節點都只有乙個或0個指向它的結點(父結點),只有根結點不會有父結點。每個節點都含有r條鏈結,r為字母表的大小,如果字元都由26個小寫英文本母構成,則r為26;如果字元屬於ascii字符集,則r=128;dna研究中用4個字母表示4個鹼基,r=4。

r條鏈結對應可能出現的字元,這其中會有大量的空鏈結,鍵是由從根節點到含有非空值的結點的路徑所隱式表示的。每個結點也含有乙個相應的值,可以是空也可以是符號表中的某個鍵所關聯的值。值為空的結點在符號表中沒有對應的鍵,它們的存在是為了簡化單詞查詢樹中的查詢操作,每個鍵所關聯的值儲存在給定鍵的最後乙個字母所對應的結點中。

也將基於含有r個字元的字母表的單詞查詢樹稱為r向單詞查詢樹。

查詢在單詞查詢樹中查詢給定字串鍵所對應的值時,是以被查詢的鍵中的字元為導向的。單詞查詢樹中的每個結點都包含了下乙個可能出現的所有字元的鏈結。從根結點開始,首先經過的是鍵的首字母所對應的鏈結,在下乙個結點沿著第二個字元所對應的鏈結繼續前進,以此類推,直到找到鍵的最後乙個字母所指向的結點,或者遇到了一條空鏈結。

public value get(string key) 

private node get(node x, string key, int d)

插入

插入的時候,也需要先進行一次查詢

public void put(string key, value val) 

public node put(node x, string key, value val, int d)

char c = key.charat(d);

x.next[c] = put(x.next[c], key, val, d + 1);

return x;

}

查詢所有鍵

單詞查詢樹中的字元是被隱式地表示的,查詢的時候需要顯式地將它們表示出來,並加入到佇列中。查詢基於乙個叫做collect的方法,它的引數中包含了乙個字串,用來儲存從根結點出發的路徑上的一系列字元。每當在collect()呼叫中訪問乙個結點時,方法的第乙個引數就是這個結點,第二個引數是從根節點到這個結點的路徑上的所有字元。如果結點的值非空,就將和它相關聯的字串加入佇列中,然後遞迴地訪問它的鏈結陣列所指向的所有可能的字元結點。在每次呼叫collect之前,都將鏈結對應的字元附加到當前鍵的末尾作為引數。要實現keys()方法,可以用空字元作為引數呼叫keyswithprefix()方法。要實現keyswithprefix(),則可以先呼叫get()方法找出給定字首所對應的單詞查詢子樹,再使用collect()。

public iterablekeys() 

public iterablekeyswithprefix(string pre)

private void collect(node x, string pre, queueq)

}

萬用字元匹配

萬用字元匹配的過程類似keyswithprefix,但需要為collect新增乙個用於指定匹配模式的引數。模式中用'.'來表示萬用字元,如果模式中含有萬用字元,就需要用遞迴呼叫處理所有的鏈結,否則就只需要處理模式中指定字元的鏈結即可。

public iterablekeysthatmatch(string pat) 

private void collect(node x, string pre, string pat, queueq)

}

最長字首

longestprefixof方法會找出與給定字串匹配的最長字首。比如對於鍵by,she, shells,longestprefixof("shell")的結果為she。要找到最長字首,需要乙個類似於get的遞迴方法來記錄查詢路徑上所找到的最長鍵的長度,並在遇到值非空的結點時更新它,然後在被查詢的字串結束或者遇到空鏈結時終止查詢。

public string longestprefixof(string s) 

public int search(node x, string s, int d, int length)

刪除

要從單詞查詢樹中刪除乙個鍵值對,首先需要找到鍵所對應的結點並將它的值設為空。然後分兩種情況:

public void delete(string key) 

private node delete(node x, string key, int d)

if (x.val != null)

return x;

for (char c = 0; c < r; c++)

if (x.next[c] != null)

return x;

return null;

}

r向單詞查詢樹的性質

單詞查詢樹的鏈結結構和鍵的插入或刪除順序無關,對於任意給定的一組鍵,它們的單詞查詢樹都是唯一的,這與之前所有的其它查詢樹都不相同。

在單詞查詢樹中查詢或插入乙個鍵時,訪問陣列的次數最多為鍵的長度加1,因為get()和put()都使用了乙個指示字元位置的引數d,它的初始值為0,每次遞迴都會加1,當長度等於鍵的長度時遞迴停止,此時訪問了陣列d+1,如果查詢未命中,訪問次數會更少。這說明在單詞查詢樹中查詢乙個鍵所需的時間與樹的大小無關,只與鍵的長度有關。

關於單詞查詢樹占用的空間,與樹中的鏈結總數有關。設w為鍵的平均長度,r為字符集的大小,n為鍵的總數,則一顆單詞查詢樹中的鏈結總數在rn到rnw之間。

有一些經驗性的規律:當所有鍵都較短時,鏈結的總數接近於rn;而當所有鍵都較長時,鏈結的總數接近於rnw,所以縮小r或w能夠節省大量的空間。而且在實際應用中,使用單詞查詢樹之前,首先了解將要插入的所有鍵的性質是非常重要的。

r向單詞查詢樹雖然檢索速度很快,但空間占用也非常大,尤其是對於比較大的字符集和比較長的鍵,這將消耗非常大的空間。三向單詞查詢樹可避免這個問題。

在三向單詞查詢樹中,每個節點都含有乙個字元,三條鏈結和乙個值。這三條鏈結分別對應著當前字母小於、等於、大於節點字母的所有鍵。只有沿著中間鏈結前進時才會找到待查詢的鍵。

在三向單詞查詢樹中查詢鍵時,首先將鍵的首字母和根結點進行比較,如果首字母較小,就選擇左鏈結,如果首字母較大,就選擇右鏈結,首字母與根節點字元相等,就選擇中鏈結,然後遞迴查詢,直到遇到乙個空鏈結或者當鍵結束時結點的值為空,則查詢未命中;如果在鍵結束時結點的值非空,則查詢命中。

public class tst

public value get(string key)

private node get(node x, string key, int d)

public void put(string key, value val)

private node put(node x, string key, value val, int d)

}

三向單詞查詢樹的性質

三向單詞查詢樹可以看作是r向單詞查詢樹的緊湊表示,但三向單詞查詢樹的形狀是與鍵的插入順序有關的,而且空間占用要比r向單詞查詢樹小很多。

三向單詞查詢樹的每個結點只含有3個鏈結,樹的鏈結總數在3n到3nw之間。

單詞查詢樹

兩種方法 給出一些列號碼,若果任乙個號碼不在另乙個中充當字首,那麼這系列號碼是合理的輸出yes,否則輸出no 思路 標頭檔案中find函式的使用,按長度從小到大排列,那麼能當另乙個號碼字首的只能是前乙個當後乙個的字首,所以乙個乙個找 時間複雜度 o n include include include...

單詞查詢樹

一 概念 從上面的圖中,我們或多或少的可以發現一些好玩的特性。第一 根節點不包含字元,除根節點外的每乙個子節點都包含乙個字元。第二 從根節點到某一節點,路徑上經過的字元連線起來,就是該節點對應的字串。第三 每個單詞的公共字首作為乙個字元節點儲存。二 使用範圍 既然學trie樹,我們肯定要知道這玩意是...

單詞查詢樹

在進行文法分析的時候,通常需要檢測乙個單詞是否在我們的單詞列表裡。為了提高查詢和定位的速度,通常都畫出與單詞列表所對應的單詞查詢樹,其特點如下 1 根結點不包含字母,除根結點外每乙個結點都僅包含乙個大寫英文本母 2 從根結點到某一結點,路徑上經過的字母依次連起來所構成的字母序列,稱為該結點對應的單詞...