leetcode:实现 Trie (前缀树)

题目来源:力扣

题目描述:

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
=============================================================
示例:
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true
=============================================================
说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

审题:

题目很直接,实现单词前缀树Trie结构,且支持insert, search, startsWith三个方法,幸好不需要实现复杂的delete方法,哈哈哈.

单词前缀树的实现,常见的包括R向单词查找树即三向单词查找树.当字符集较少时,可使用R向单词查找树,而当字符集较大时,R向单词查找树空间复杂度非常大,因此通常使用三向单词树实现.

该问题中由于字符串仅包含小写字母,因此我们可以考虑使用R向单词查找树和三向单词查找树实现Trie结构.

R向单词查找树:

此处简单介绍R向单词查找树的基本结构.在R向单词查找树中,在R向单词查找树中,每一节点的链接使用长度为R的数组表示, 节点的链接指向当前字符的后续字符节点.例如,根节点的链接数组保存的即是字符串的第一个字符节点.

假设当前树中仅包含字符串"ab", 字符’a’在小写字母字符集中的次序为1,因此根节点的链接数组中第1个元素不为空.沿根节点链接数组中第1个元素向下,由于字符’b’在小写字母字符集中的次序为2,因此该节点的链接数组中第2个元素不为空.再往下移动到链接数组中第2个元素,由于后续没有其他字符,因此该节点的链接数组中各元素均为空.由于该节点是字符串"ab"的结尾,因此标记当前节点isEnd = true.

如果字符集非常大,则R向单词查找树中每一节点存在大量空链接,因此考虑使用三向单词 查找树结构实现单词查找树.

三向单词查找树

三向单词查找树的节点包含三个链接left, mid, right,并且每一节点都有一个值域存储该节点保存的字符.节点的left链接指向小于当前节点字符的其余节点,mid链接指向等于当前节点字符的节点,right指向大于当前节点字符的节点.

以插入字符串为例,当考虑在当前节点处插入字符串的第i个字符时,若当前节点为空,则创建新节点,保存字符串第i个字符.若当前节点不为空,则包含如下三种情形:如果当前节点字符大于字符串第i个字符,则沿left链接移动,向left链接插入当前字符的第i个字符,如果当前节点字符小于字符串第i个字符,则沿right链接移动,向right链接指向的节点出入字符串的第i个字符.如果当前节点字符等于字符串第i个字符,则我们沿mid链接移动,在mid链接节点处插入字符串第i+1个字符.

java算法实现

R向单词查找树:

//由于仅包含小写字母,因此考虑使用R向单词查找树结构

class Trie {
    private Node root;
    class Node{
        boolean isEnd; //标记当前节点是否是某一字符串的结尾.(由结尾字符链接指向)
        Node[] next = new Node[26];
    }

    /** Initialize your data structure here. */
    public Trie() {
    }
    
    //将最后一个节点的isEnd设置为True
    private Node insert(Node x, String word, int d){
        if(x == null){ //如果当前节点为空,则新创建一个节点.
            x = new Node();
        }

        if(d == word.length()){ //如果已经插入了d个字符,则当前节点isEnd为true
            x.isEnd = true;
            return x;
        }
        else
        	//添加指向第d个字符链接,即表示插入第d个字符
            x.next[word.charAt(d)-'a'] = insert(x.next[word.charAt(d)-'a'], word, d+1);
        return x; 
    }

    /** Inserts a word into the trie. */
    public void insert(String word) {
        root = insert(root, word, 0);
    }
    
    private boolean search(Node x, String word, int d){
        if(x == null) //如果当前节点为null,非空字符串word不在前缀树中.
            return false;
        //如果x不为空,则word.charAt(d-1)存在
        if(d == word.length()){
        //如果d==word.length(), 则当前字符串的所有字符均已判断,判断当前节点是否是结尾字符
            return x.isEnd;
        }
        else
        	//判断word.charAt(d)
            return search(x.next[word.charAt(d)-'a'], word, d+1);
    }

    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        return search(root, word, 0);
    }
    
    //startsWith思路与search一致,只不过对于startsWith无需判断最后一个节点是否是结尾字符,只要当前前缀树中能够搜索到prefix即返回true
    private boolean startsWith(Node x, String prefix, int d){
        if(x == null)
            return false;
        if(d == prefix.length())
            return true;
        else
            return startsWith(x.next[prefix.charAt(d)-'a'], prefix, d+1);
    }
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        return startsWith(root, prefix, 0);
    }
}

三向查找树

//三向单词查找树

class Trie {
    private Node root;

    class Node{
        private boolean isEnd;
        private char c; //保存当前节点对应的字符
        private Node left; //指向小于当前字符c的节点链接
        private Node mid; //指向等于字符c的节点链接
        private Node right; //指向大于字符c的节点链接
        Node(char c, Node left, Node mid, Node right){
            this.c = c;
            this.left = left;
            this.mid = mid;
            this.right = right;
        }
    }

    /** Initialize your data structure here. */
    public Trie() {
    }
    
    private Node insert(Node x, String word, int d){
        if(x == null){
            x = new Node(word.charAt(d), null, null, null);
        }

        if(word.charAt(d) > x.c){
            x.right = insert(x.right, word, d);
        }
        else if(word.charAt(d) < x.c){
            x.left = insert(x.left, word, d);
        }
        else if(d < word.length()-1) 
            x.mid = insert(x.mid, word, d+1);
        else //如果已经插入了所有d个字符,则当前节点为结尾字符对应的节点
            x.isEnd = true;
        return x;
    }
    /** Inserts a word into the trie. */
    public void insert(String word) {
        root = insert(root, word, 0);
    }
    
    private boolean search(Node x, String word, int d){
        if(x == null)
            return false;
        //判断当前字符第d位
        if(word.charAt(d) > x.c)
            return search(x.right, word, d);
        else if(word.charAt(d) < x.c)
            return search(x.left, word, d);
        else if(d < word.length() - 1)
            return search(x.mid, word, d+1);
        else
            return x.isEnd;
    }
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        return search(root, word, 0);
    }
    
    private boolean startsWith(Node x, String prefix, int d){
        if(x == null)
            return false;

        if(prefix.charAt(d) > x.c)
            return startsWith(x.right, prefix, d);
        else if(prefix.charAt(d) < x.c)
            return startsWith(x.left, prefix, d);
        else if(d < prefix.length()-1)
            return startsWith(x.mid, prefix, d+1);
        else
            return true;
    }
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        return startsWith(root, prefix, 0);
    }
}

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