題目來源:力扣
題目描述:
實現一個 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);
}
}