本文只是自己的筆記,並不具備過多的指導意義。
前綴樹
何爲前綴樹
前綴樹又名字典樹,單詞查找樹,Trie樹,是一種多路樹形結構,是哈希樹的變種,和hash效率有一拼,是一種用於快速檢索的多叉樹結構。多用於詞頻搜索或者模糊查詢。
查詢時只與單樣本長度有關,而與樣本量無關。
舉例:
給出一組單詞,inn, int, at, age, adv,ant, 我們可以得到下面的Trie:
如此,在進行依次輸入進行查詢時。只需要順着之前的樹繼續查詢即可,而不需要每次修改字符串都遍歷所有信息。
在刪除了字符時,也只需要回滾到上層即可。
基本代碼結構
-
樹節點
一個多叉樹結構的節點。前綴樹的功能,取決於節點的健壯性。
//前綴樹節點
public class TrieNode {
public var end :Bool //是否爲某個單詞最後節點,查詢用
public var path :Int //記錄有幾個字符串經過了這個節點,刪除用
public var nexts:Dictionary<String, TrieNode> //子樹路徑,基本功能
public init() {
self.end = false
self.nexts = Dictionary.init()
self.path = 0
}
}
其中子樹路徑,如果只存儲字符串的話。當樣本量太大可以乾脆使用數組[26]
的形式存儲。
-
前綴樹結構
持有一個根節點,具備增刪改查的功能
//前綴樹
public class Trie {
private var root:TrieNode
public init() {
self.root = TrieNode.init()
}
/// 添加字符串
public func insert(word:String) {
...
}
//查找
public func search(word:String) -> Bool {
...
}
//刪除
public func delete(word:String) {
...
}
}
-
插入數據
將字符串按字母分割,從根節點依次追加進前綴樹
需要注意如果樹中已經存在該節點路徑,則複用
/// 添加字符串
public func insert(word:String) {
if word == nil {
return
}
let strs = wordToArr(word: word)
if strs.count == 0 {
return
}
//從根節點開始建立前綴樹
var node = root
for i in 0..<strs.count {
if node.nexts[strs[i]] == nil {
node.nexts.updateValue(TrieNode.init(), forKey: strs[i]) //沒有可服用路徑,新建節點
}
node = node.nexts[strs[i]]! //node跳到下層
node.path+=1 // 將當前節點計數+1
}
node.end = true //標記最後一個節點
}
-
查找數據
查找與插入類似,但需要判斷最後一個節點的end屬性是否被標記
//查找
public func search(word:String) -> Bool {
if word == nil {
return false
}
let strs = wordToArr(word: word)
if strs.count == 0 {
return false
}
//從根節點開始建立前綴樹
var node = root
for i in 0..<strs.count {
if node.nexts[strs[i]] == nil {
return false //任何一步有空,說明未被插入過
}
node = node.nexts[strs[i]]!
}
return node.end //返回最後一個節點的標記狀態
}
-
刪除數據
先類似查找的方式確定是否存在該數據,然後嘗試刪除。
每次刪除,其實是將節點的path屬性-1。當path屬性==0時,從上級節點的nexts列表中remove掉該節點即可return結束。
//刪除
public func delete(word:String) {
if word == nil {
return
}
let strs = wordToArr(word: word)
if strs.count == 0 {
return
}
//嘗試查找指定字符串,並且保存在nodes備用
var node = root
var nodes : [TrieNode] = Array.init()
nodes.append(node)
for i in 0..<strs.count {
if node.nexts[strs[i]] == nil {
return
}
node = node.nexts[strs[i]]!
nodes.append(node) //將查詢到的節點放入數組備用
}
//如果最後一個節點不是end,說明不存在該字符串
if node.end == true{
//遍歷nodes數組,當某個節點path==1說明可以直接刪除該節點了
for i in 1..<nodes.count { //從1開始,0是root節點
if nodes[i].path == 1 {
nodes[i-1].nexts.removeValue(forKey: strs[i-1])
return
}
nodes[i].path -= 1
}
}
}
前綴樹的應用
- 字符串的快速檢索
前綴樹的查詢時間複雜度是O(L),L是字符串的長度。所以效率還是比較高的。字典樹的效率比hash表高。 - 字符串排序
多叉樹本身已經是有序的,只要按照每層都先遍歷低位節點的方式即可。
後綴樹
相對於前綴樹來說,後綴樹並不是針對大量字符串的,而是針對一個或幾個字符串來解決問題。比如字符串的迴文子串,兩個字符串的最長公共子串等等。
比如blanana
這個單詞,在後綴樹的結構中將以如下結構展現(爲了方便看到後綴,我沒有合併相同的前綴)
後綴樹的應用
- 查找字符串
an
是否在banana
中
如上圖所致,a
+n
的節點順序已經存在於後綴樹中,所以返回true
- 查找字符串
a
+n
在banana
中重複的次數。
如上圖所致,a
+n
中n
節點的path
計數爲2,所以重複次數爲2.