Trie树

本文介绍了三种数据结构。线段树是二叉树形数据结构,用于存储区间,空间复杂度为O(n),查询时间复杂度为O(log n + k)。红黑树是自平衡二叉查找树,可在O(log n)时间内完成查找、插入和删除。Trie树又称前缀树,常用于搜索提示,插入和查询时间复杂度为O(m)。

线段树Segment tree

线段树(英语:Segment tree)是一种二叉树形数据结构,用以存储区间或线段,并且允许快速查询结构内包含某一点的所有区间。
一个包含 n个区间的线段树,空间复杂度为 O(n),查询的时间复杂度则为 O(\log n+k)} ,其中k是匹配条件的区间数量。此数据结构亦可推广到高维度。
在这里插入图片描述
在这里插入图片描述

红黑树Red–black tree

红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。被称为"对称二叉B树",红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在(\log n)}时间内完成查找,插入和删除,这里的 n是树中元素的数目。

Trie

https://leetcode.com/problems/implement-trie-prefix-tree/solution/
在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
在图示中,键标注在节点中,值标注在节点之下。每一个完整的英文单词对应一个特定的整数。Trie可以看作是一个确定有限状态自动机,尽管边上的符号一般是隐含在分支的顺序中的。

键不需要被显式地保存在节点中。图示中标注出完整的单词,只是为了演示trie的原理。

在这里插入图片描述

应用场景

trie树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
在这里插入图片描述

trie树实际上是一个DFA(deterministic finite automaton,确定有限状态自动机),通常用转移矩阵表示。行表示状态,列表示输入字符,(行,列)位置表示转移状态。这种方式的查询效率很高,但由于稀疏的现象严重,空间利用效率很低。也可以采用压缩的存储方式即链表来表示状态转移,但由于要线性查询,会造成效率低下。

在计算理论中,确定有限状态自动机或确定有限自动机(英语:deterministic finite automaton, DFA)是一个能实现状态转移的自动机。对于一个给定的属于该自动机的状态和一个属于该自动机字母表 {\displaystyle \Sigma } \Sigma 的字符,它都能根据事先给定的转移函数转移到下一个状态(这个状态可以是先前那个状态)。

特点

字典树主要有如下三点性质:

  1. 根节点不包含字符,除根节点意外每个节点只包含一个字符。

  2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

  3. 每个节点的所有子节点包含的字符串不相同。

There are several other data structures, like balanced trees and hash tables, which give us the possibility to search for a word in a dataset of strings. Then why do we need trie? Although hash table has O(1) time complexity for looking for a key, it is not efficient in the following operations :

  • Finding all keys with a common prefix.
  • Enumerating a dataset of strings in lexicographical order.
    Another reason why trie outperforms hash table, is that as hash table increases in size, there are lots of hash collisions and the search time complexity could deteriorate to O(n), where nn is the number of keys inserted. Trie could use less space compared to Hash Table when storing many keys with the same prefix. In this case using trie has only O(m)O(m) time complexity, where mm is the key length. Searching for a key in a balanced tree costs O(m \log n)O(mlogn) time complexity.

Trie node structure

Trie是一棵有根的树。其节点具有以下字段: 到其子节点的RR链接的最大值,其中每个链接对应于数据集字母表中的一个RR字符值。在本文中,我们假设RR为26,即小写拉丁字母的数量。 布尔字段,指定节点是对应于键的末尾,还是仅仅是键前缀。

Two of the most common operations in a trie are insertion of a key and search for a key.

Insertion of a key to a trie

We insert a key by searching into the trie. We start from the root and search a link, which corresponds to the first key character. There are two cases :

  • A link exists. Then we move down the tree following the link to the next child level. The algorithm continues with searching for the next key character.
  • A link does not exist. Then we create a new node and link it with the parent’s link matching the current key character. We repeat this step until we encounter the last character of the key, then we mark the current node as an end node and the algorithm finishes.

Complexity Analysis

Time complexity : O(m)O(m), where m is the key length.
In each iteration of the algorithm, we either examine or create a node in the trie till we reach the end of the key. This takes only mm operations.

Space complexity : O(m)O(m).
In the worst case newly inserted key doesn’t share a prefix with the the keys already inserted in the trie. We have to add mm new nodes, which takes us O(m)O(m) space.

class WordDictionary {
public:
    /** Initialize your data structure here. */
    WordDictionary() {
        
    }
    
    /** Adds a word into the data structure. */
    void addWord(string word) {
        // word的长度作为key? 二维数组,同样长度的排在一起
        words[word.size()].push_back(word);
        
    }
    
    /** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
    bool search(string word) {
        // 其实在遍历vector, for(declaration: expression)
        // expression是一个对象,用于表示一个序列,可以是vector对象、string对象等等;
        // declaratin是定义一个变量,用于表示访问序列中的基础元素
        for(auto s: words[word.size()])
            if(isEqual(s, word))
                return true;
        return false;
        
        // 两种遍历方式!或者
        // unordered_map<int, vector<string>>:: iterator itr; 
        // umap = words[word.size()];
        // for (itr = umap.begin(); itr != umap.end(); itr++){}
        
    }
    
private:
    // 同一长度会有很多个单词,所以这些通长度单词是用vector来保存的
    unordered_map<int, vector<string>> words;
    
    bool isEqual(string a, string b){
        for(int i = 0; i < a.size(); i++){
            if(b[i] == '.') continue;
            if(b[i] != a[i]) return false;
        }
        return true;
    }
};

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary obj = new WordDictionary();
 * obj.addWord(word);
 * bool param_2 = obj.search(word);
 */
### Trie的实现原理 Trie(又称前缀)是一种多叉结构,主要用于高效地处理字符串集合。其核心思想是通过共享字符串的前缀部分来减少存储空间加快查找速度。每个节点代表一个字符,而从根节点到某一子节点的路径组成一个字符串,表示该字符串的存在。 Trie的节点通常包含以下两个主要属性: - **isKey**:布尔值,标记该节点是否为某个完整字符串的结尾。 - **children**:一个指针数组,指向该节点的子节点。数组的大小通常取决于字符集的大小(例如,对于英文小写字母来说,大小为26)。 Trie的基本操作包括插入、查找删除: - **插入操作**:从根节点开始,依次匹配字符串中的每个字符。如果字符不存在,则创建新节点。当整个字符串插入完成后,将最后一个节点的`isKey`标记为`true`。 - **查找操作**:从根节点出发,匹配字符串中的每个字符。如果中途字符无法匹配,则说明字符串不存在;如果所有字符都匹配成功,还需检查最后一个节点的`isKey`标志,以确认是否为完整字符串。 - **删除操作**:首先确认字符串是否存在于Trie中,如果存在,则从字符串的最后一个字符开始逐层回溯删除节点,直到遇到共享前缀的字符为止。 以下是一个简单的Trie实现的Java代码示例: ```java class TrieNode { private boolean isKey; // 是否为关键词结尾 private TrieNode[] children; // 子节点指针 public TrieNode() { this.isKey = false; this.children = new TrieNode[26]; // 假设只包含小写字母 } public boolean containsKey(char ch) { return children[ch - 'a'] != null; } public TrieNode get(char ch) { return children[ch - 'a']; } public void put(char ch, TrieNode node) { children[ch - 'a'] = node; } public void setKey(boolean key) { isKey = key; } public boolean isKey() { return isKey; } } public class Trie { private TrieNode root; public Trie() { root = new TrieNode(); } // 插入字符串 public void insert(String word) { TrieNode node = root; for (int i = 0; i < word.length(); i++) { char currentChar = word.charAt(i); if (!node.containsKey(currentChar)) { node.put(currentChar, new TrieNode()); } node = node.get(currentChar); } node.setKey(true); } // 查找字符串 public boolean search(String word) { TrieNode node = root; for (int i = 0; i < word.length(); i++) { char currentChar = word.charAt(i); if (!node.containsKey(currentChar)) { return false; } node = node.get(currentChar); } return node != null && node.isKey(); } // 检查前缀是否存在 public boolean startsWith(String prefix) { TrieNode node = root; for (int i = 0; i < prefix.length(); i++) { char currentChar = prefix.charAt(i); if (!node.containsKey(currentChar)) { return false; } node = node.get(currentChar); } return node != null; } } ``` ### Trie的应用场景 Trie因其高效的前缀匹配特性,在多个领域有着广泛的应用,例如: - **搜索引擎的关键词提示**:用户在搜索框输入时,Trie可以快速提供与输入前缀匹配的关键词建议,提升用户体验[^3]。 - **拼写检查**:Trie可以用于拼写检查工具中,通过查找与输入单词相似的正确拼写来纠正错误。 - **自动补全**:在输入框中,如浏览器地址栏或命令行界面,Trie可以用于自动补全功能,根据用户输入的部分字符提供完整的建议。 - **IP路由**:在网络路由中,Trie可以用于最长前缀匹配问题,快速找到最佳的路由路径。 - **词典实现**:Trie可以用于构建高效的词典系统,支持快速的单词插入、查找删除操作。 由于Trie的高效性,它在处理大量字符串数据时表现尤为出色。然而,Trie并不是所有场景下的最优选择。对于动态集合数据的查找,散列表或红黑可能更为合适。Trie的优势在于其前缀匹配能力,因此更适合于需要频繁进行前缀匹配查询的场景[^3]。 ### 相关问题 1. Trie与哈希表相比有哪些优缺点? 2. 如何优化Trie以减少内存占用? 3. Trie在拼写检查中的具体实现方式是什么? 4. Trie如何支持通配符匹配? 5. Trie能否支持中文字符的处理?如果可以,如何实现?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值