Chapter 18: Tries

前言

The trie (pronounced as try) is a tree that specializes in storing data that can be represented as a collection, such as English words.

大綱

  • Example

  • Implementation

    • TrieNode

    • Trie

    • Insert

    • Contains

    • Remove

    • Prefix matching

Example

什麼情況會用到trie這個結構。ex. 進行字串比對

利用暴力比對法

  • The time complexity of words(matching:) is O(k*n), where k is the longest string in the collection, and n is the number of words you need to check

class EnglishDictionary {

  private var words: [String]

  func words(matching prefix: String) -> [String] {
    return words.filter { $0.hasPrefix(prefix) }
  }
}

TrieNode

public class TrieNode<Key: Hashable> {

    // key: 存的就是node中的資料,設成optional是因為trie的root是不會有key
    public var key: Key?

    // weak是用在後面的remove中
    public weak var parent: TrieNode?

    // 在trie中每個node會有很多children,利用dict來記錄這些children
    public var children: [Key: TrieNode] = [:]

    // 是否是一個collection的結束位置
    // ex. 一個字串中的最後一個字元
    public var isTerminating = false

    public init(key: Key?, parent: TrieNode?) {
        self.key = key
        self.parent = parent
    }
}

Trie

// The Trie class is built for all types that adopt the Collection protocol
// Each element inside the collection must be Hashable.
// This is required because you’ll use the collection’s elements as keys for the children dictionary in TrieNode.

public class Trie<CollectionType: Collection> where CollectionType.Element: Hashable {

    public typealias Node = TrieNode<CollectionType.Element>

    public let root = Node(key: nil, parent: nil)

    public init() {}

}

Insert

時間複雜度: O(K), k是collection的長度,因為要重複k次,把collection每個元素一一進行插入。

    public func insert(_ collection: CollectionType) {

        var current = root

        for element in collection {
            // 當前node中並沒有此elemnt的child
            if current.children[element] == nil {
                // 替當前node新增一個child
                current.children[element] = Node(key: element, parent: current)
            }
            // 繼續往下長
            current = current.children[element]!
        }

        // 走完上面迴圈,表示現在current已經走到collection尾端了
        current.isTerminating = true
    }

Contains

時間複雜度: O(K), k是collection的長度,因為要重複k次,把collection每個元素一一進行檢查確認。

    public func contains(_ collection: CollectionType) -> Bool {
        var currnt = root
        for element in collection {
            guard let child = currnt.children[element] else {
                // 目前node的child中並沒有當前元素,所以這個trie並沒包含這個collection
                return false
            }
            currnt = child
        }

        // 走到最末端,還要確認當前是否是終止節點
        return currnt.isTerminating
    }

Remove

時間複雜度: O(K), k是collection的長度,因為要重複k次,把collection每個元素一一進行刪除。

    public func remove(_ collection: CollectionType) {
        var current = root
        // 確認是否可以trie中走到collection最後一個元素
        for element in collection {
            guard let child = current.children[element] else {
                return
            }
            current = child
        }

        // 確保是結束節點
        guard current.isTerminating else {
            return
        }

        // 開始進行刪除
        current.isTerminating = false

        // current.children.isEmpty: 當前被刪除的元素是否是被其他collection所共用
        // current.isTerminating: collection中每個元素應該都不能是結束節點
        while let parent = current.parent, current.children.isEmpty && !current.isTerminating {
            // 清除
            parent.children[current.key!] = nil
            // 從末端往回一一清除
            current = parent
        }
    }

Prefix matching

這個Trie最經典有名的應用了

時間複雜度: k: 是在collection中符合prefix的長度, m: 是在目前collection中符合當前prefix的數量,原本暴力算法是n,每次都要比對原始collection的數量, 透過trie讓m會小於n

collection(startingWith:) has a time complexity of O(km), where k represents the longest collection matching the prefix and m represents the number of collections that match the prefix. Recall that arrays have a time complexity of O(kn), where n is the number of elements in the collection.

// 我們會用RangeReplaceableCollection中的append方法
public extension Trie where CollectionType: RangeReplaceableCollection {

    func collections(startingWith prefix: CollectionType) -> [CollectionType] {
        var current = root
        for element in prefix {
            guard let child = current.children[element] else {
                // 在tril中找不到任何符合的prefix
                return []
            }
            current = child
        }

        // 現在這個node之後的所有child都符合此prefix
        return collections(startingWith: prefix, after: current)
    }

    // 找出此node之後所有組合
    private func collections(startingWith prefix: CollectionType,
                             after node: Node) -> [CollectionType] {

        var results: [CollectionType] = []

        if node.isTerminating {
            results.append(prefix)
        }

        for child in node.children.values {
            var prefix = prefix
            prefix.append(child.key!)
            // 遞迴,繼續往下找組合
            results.append(contentsOf: collections(startingWith: prefix,
                after: child))
        }

        return results
    }

}

Last updated

Was this helpful?