死磕算法-前缀树

什么是Trie树?

Trie树,又叫字典树,单词查找树或键树,是一种多叉树结构。
在这里插入图片描述
上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。从上图可以归纳出Trie树的基本性质:

  1. 根节点不包含字符,除根节点外的每一个子节点都包含一个字符
  2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串
  3. 每个节点的所有子节点包含的字符互不相同

通常在实现的时候,会在节点结构中设置成一个标志,用来标记该结点处是否构成一个单词(关键字)
可以看出,Trie树的关键字一般都是字符串,而且Trie树把每个关键字保存在一条路径上,而不是一个结点中。另外,两个有公共前缀的关键字,在Trie树中前缀部分的路径相同,所以Trie树又叫做前缀树(Prefix Tree)

Trie树的优缺点

Trie树的核心思想是空间换时间,利用字符串的公共前缀来减少无谓的字符串比较以达到提高查询效率的目的

优点
  1. 插入和查询的效率很高,都为 O ( m ) O(m) O(m),其中 m m m 是待插入/查询的字符串的长度
  2. Trie树中不同的关键字不会产生冲突
  3. Trie树只有在允许一个关键字关联多个值的情况下才有类似hash碰撞发生
  4. Trie树不用求 hash 值,对短字符串有更快的速度。通常,求hash值也是需要遍历字符串的
  5. Trie树可以对关键字按字典序排序
缺点
  1. 空间消耗比较大
  2. 当 hash 函数很好时,Trie树的查找效率会低于哈希搜索

Trie树的插入

现在我们根据Trie树的性质来创建一个Trie树。假设我们现在有b,abc,abd,bcd,abcd,efg,hii 这6个单词,构建出的Trie树如下图所示:
在这里插入图片描述
 搭建Trie的方法很简单,其实就是将单词的每个字母逐一插入Trie树。插入前先看字母对应的节点是否存在,存在则共享该节点,不存在则创建对应的节点。比如要插入新单词and,就有下面几步:

  1. 插入第一个字母"a",发现root节点存在子节点a,则共享节点a
  2. 插入第二个字母"n",发现节点a存在子节点n,则共享节点n
  3. 插入第三个字母"d",发现节点n不存在子节点d,则创建子节点d。
  4. 至此,单词and中所有字母已被插入Trie树中,然后设置节点d中的标志位,标记路径root->a->n->d这条路径上所有节点的字符可以组成一个单词and

Trie树的查询

从root节点开始按照单词的字母顺序向下遍历Trie树,遍历完成有两种情况:

  1. 单词中每个字母都在Trie树中被查找过,此时Trie树不一定被遍历完
  2. 单词中部分字母未在Trie树中被查找过,此时Trie树一定被遍历完

查询单词是否存在,我们不会管遍历完成时是上面的哪种情况,我们只需要关注遍历结束时Trie树最后一个被遍历的节点last。若节点last中设置了标志位(即表示路径root->…->last上所有节点的字符可以组成一个单词)则表示被查询的单词存在于Trie树中,否则表示不存在

算法思想

Trie的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的

Trie树的实现

public class Code_01_TrieTree {

	public static class TrieNode {
		public int path;
		public int end;
		public TrieNode[] nexts;

		public TrieNode() {
			path = 0;
			end = 0;
			//26个字母对应26个分叉
			nexts = new TrieNode[26];
		}
	}

	public static class Trie {
		private TrieNode root;

		public Trie() {
			root = new TrieNode();
		}

		public void insert(String word) {
			if (word == null) {
				return;
			}
			char[] chs = word.toCharArray();
			TrieNode node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				//字符型相减结果为整数,index为0,对于字符为'a';index为1,对应字符为'b'...
				index = chs[i] - 'a';
				if (node.nexts[index] == null) {
					node.nexts[index] = new TrieNode();
				}
				//若存在则返回node,node.path++
				node = node.nexts[index];
				node.path++;
			}
			//node.end++,用于计数字符串出现次数,多个字符串则累加计数
			node.end++;
		}

		public void delete(String word) {
			//先判断是否存在
			if (search(word) != 0) {
				char[] chs = word.toCharArray();
				TrieNode node = root;
				int index = 0;
				for (int i = 0; i < chs.length; i++) {
					index = chs[i] - 'a';
					if (--node.nexts[index].path == 0) {
						node.nexts[index] = null;
						return;
					}
					node = node.nexts[index];
				}
				node.end--;
			}
		}
		
		//返回字符串中最后字符对应的end节点
		public int search(String word) {
			if (word == null) {
				return 0;
			}
			char[] chs = word.toCharArray();
			TrieNode node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = chs[i] - 'a';
				if (node.nexts[index] == null) {
					return 0;
				}
				node = node.nexts[index];
			}
			return node.end;
		}
		
		//返回前缀
		public int prefixNumber(String pre) {
			if (pre == null) {
				return 0;
			}
			char[] chs = pre.toCharArray();
			TrieNode node = root;
			int index = 0;
			for (int i = 0; i < chs.length; i++) {
				index = chs[i] - 'a';
				if (node.nexts[index] == null) {
					return 0;
				}
				node = node.nexts[index];
			}
			return node.path;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值