题目来源:力扣
题目描述:
实现一个 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);
}
}