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);
    }
}

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