數據結構基礎--前綴樹&&後綴樹

本文只是自己的筆記,並不具備過多的指導意義。

前綴樹

何爲前綴樹

前綴樹又名字典樹,單詞查找樹,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
        }
    }
}

前綴樹的應用

  1. 字符串的快速檢索
    前綴樹的查詢時間複雜度是O(L),L是字符串的長度。所以效率還是比較高的。字典樹的效率比hash表高。
  2. 字符串排序
    多叉樹本身已經是有序的,只要按照每層都先遍歷低位節點的方式即可。

後綴樹

相對於前綴樹來說,後綴樹並不是針對大量字符串的,而是針對一個或幾個字符串來解決問題。比如字符串的迴文子串,兩個字符串的最長公共子串等等。

比如blanana這個單詞,在後綴樹的結構中將以如下結構展現(爲了方便看到後綴,我沒有合併相同的前綴)

後綴樹的應用

  1. 查找字符串an是否在banana
    如上圖所致,a+n的節點順序已經存在於後綴樹中,所以返回true
  2. 查找字符串a+nbanana中重複的次數。
    如上圖所致,a+nn節點的path計數爲2,所以重複次數爲2.

前綴樹和後綴樹

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