Trie 樹

Trie 樹肌幽,也叫字典樹,專門做字符串匹配的數(shù)據(jù)結(jié)構(gòu)糯耍,也可快速的做字符串前綴匹配扔字。

它是一種多叉樹,即把前綴相同的字符串合并在一起温技,根節(jié)點默認(rèn)不存儲字符革为。如下圖所示:

image.png

上圖中 hiher舵鳞、how 共用節(jié)點 h震檩,它們相同的前綴是 hhelloher 也是同理蜓堕,相同前綴是 he抛虏。

舉個栗子,hi 套才、her迂猴、how 的插入過程如下:

image.png

那么如何進(jìn)行查找呢?

栗子1 -- 查找 hi 過程:

  1. 從根節(jié)點 root 開始背伴,查找其子節(jié)點有無 h沸毁,結(jié)果是有
  2. 查找 h 的子節(jié)點有無 i峰髓,結(jié)果也有,同時 i 是末尾節(jié)點以清,表示是個完整的字符串儿普,匹配完成。

栗子2 -- 查找 he 過程:

  1. 從根節(jié)點 root 開始掷倔,查找其子節(jié)點有無 h眉孩,結(jié)果是有
  2. 查找 h 的子節(jié)點有無 e,結(jié)果也有勒葱,但 e 不是末尾節(jié)點浪汪,表示其只是匹配到了前綴,并不能完全匹配到 he凛虽。
image.png

通過上述描述死遭,我們知道其插入、查找的過程凯旋。那么如何用代碼實現(xiàn)呢呀潭?

實現(xiàn)

前面提到過,Trie 樹是多叉樹至非,節(jié)點的取值范圍在字符串的所有可能出現(xiàn)的字符集內(nèi)钠署。如字符串包含的只是 a-z,那么最多只需要 26 個子節(jié)點荒椭;如果字符串包含 a-zA-Z谐鼎,那么最多需要 52 個自己點;以此類推趣惠。

當(dāng)然狸棍,不是每個節(jié)點的子節(jié)點都會包含所有字符集,不存在的子節(jié)點設(shè)置為 null味悄。

所以草戈,假設(shè)字符集為 a-z ,總共 26 個小寫字母傍菇,其數(shù)據(jù)結(jié)構(gòu)如下:

class TrieNode {
    var data: Character
    
    // 字符集 26 個小寫字母
    var children: [TrieNode?]
    var isEnd: Bool = false
    
    init(data: Character) {
        self.data = data
        
        children = [TrieNode]()
        
        // 初始化 26 個
        var i = 0
        while i < 26 {
            children.append(nil)
            i += 1
        }
    }
}

需要取子節(jié)點時猾瘸,只需要算出 index = ch - 'a' ,然后根據(jù) p.children[index] 取出即可丢习,為 null 則表示不存在該 ch 字符牵触。

Trie 樹定義如下,包含插入和查找兩個方法咐低。

class Trie {
    let root = TrieNode(data: Character("/"))

    // 插入
    func insert(text: String) {}

    // 查找
    func match(text: String) -> Bool {}
    
    // 計算index
    func indexOfChar(_ ch: Character) -> Int {
        let index = ch.toInt() - Character("a").toInt()
        return index
    }
}

插入

逐個遍歷字符串揽思,若當(dāng)前節(jié)點不存在該字符對應(yīng)的子節(jié)點,則生成新的節(jié)點插入见擦;若存在钉汗,則沿著樹的分支繼續(xù)往下走羹令。當(dāng)遍歷完成,將最后一個節(jié)點結(jié)束符 isEnd 置為 true损痰。

func insert(text: String) {
    var p = root
    
    var i = 0
    while i < text.count {
        let strIndex = text.index(text.startIndex, offsetBy: i)
        
        let ch = text[strIndex]
        
        // 計算 index
        let index = indexOfChar(ch)
        if index < 0 || index >= 26 {
            assert(false, "包含非法字符")
        }
        
        if (index >= 0 && index <= p.children.count) {
            if p.children[index] == nil {
                // 插入新節(jié)點
                let node = TrieNode(data: ch)
                p.children[index] = node
            }
            
            p = p.children[index]!
        }
        
        i += 1
    }
    
    // 標(biāo)記結(jié)束
    p.isEnd = true
}

查找

逐個遍歷字符串福侈,若當(dāng)前節(jié)點不存在該字符對應(yīng)的子節(jié)點,則說明不匹配卢未;若存在肪凛,則沿著樹的分支繼續(xù)往下走。當(dāng)遍歷完成辽社,若最后一個節(jié)點是結(jié)束符伟墙,則完全匹配;否則只是前綴匹配滴铅。

func match(text: String) -> Bool {
        
    var p = root
    
    var i = 0
    while i < text.count {
        let strIndex = text.index(text.startIndex, offsetBy: i)
        
        let ch = text[strIndex]
        
        // 計算 index
        let index = indexOfChar(ch)
        
        if (index >= 0 && index <= p.children.count) {
            if p.children[index] != nil {
                p = p.children[index]!
            } else {
                // 不匹配
                return false
            }
        } else {
            return false
        }
        
        i += 1
    }
    
    // 完全匹配
    if p.isEnd {
        return true
    }
    
    return false
}

效率

Trie 樹構(gòu)建時戳葵,需要遍歷所有的字符串,因此時間復(fù)雜度為所有字符串長度總和 n汉匙,時間復(fù)雜度為 O(n)拱烁。

但是 Trie 樹的查找效率很高,如果字符串長度為 k噩翠,那么時間復(fù)雜度 為 O(k)邻梆。

Trie 是一種以空間換時間的結(jié)構(gòu),當(dāng)字符集較大時绎秒,會占用很多空間,同時如果前綴重合較少尼摹,空間會占用更多见芹。所以其比較適合查找前綴匹配。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蠢涝,一起剝皮案震驚了整個濱河市玄呛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌和二,老刑警劉巖徘铝,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惯吕,居然都是意外死亡惕它,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門废登,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淹魄,“玉大人,你說我怎么就攤上這事堡距〖孜” “怎么了兆蕉?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缤沦。 經(jīng)常有香客問我虎韵,道長,這世上最難降的妖魔是什么缸废? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任包蓝,我火速辦了婚禮,結(jié)果婚禮上呆奕,老公的妹妹穿的比我還像新娘养晋。我一直安慰自己,他們只是感情好梁钾,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布绳泉。 她就那樣靜靜地躺著,像睡著了一般姆泻。 火紅的嫁衣襯著肌膚如雪零酪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天拇勃,我揣著相機(jī)與錄音四苇,去河邊找鬼。 笑死方咆,一個胖子當(dāng)著我的面吹牛月腋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓣赂,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼榆骚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煌集?” 一聲冷哼從身側(cè)響起妓肢,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苫纤,沒想到半個月后碉钠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡卷拘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年喊废,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栗弟。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡操禀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出横腿,到底是詐尸還是另有隱情斤寂,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布揪惦,位于F島的核電站遍搞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏器腋。R本人自食惡果不足惜溪猿,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纫塌。 院中可真熱鬧诊县,春花似錦、人聲如沸措左。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怎披。三九已至胸嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凉逛,已是汗流浹背性宏。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留状飞,地道東北人毫胜。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像诬辈,于是被迫代替她去往敵國和親指蚁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容