算法学习06: 前缀树

前缀树(Trie树)

前缀树(Trie树)每个节点存储一个字符,从根节点向下对应一个字符串的前缀.

前缀树的用途

  1. 查询以某字符串开头的字符串: 可以实现,直接从根节点向下撸
  2. 查询是否存在"be"这个字符串? 原始的前缀树不能判断,“bef"会覆盖"be”,
    因此给节点加一个数据项,表示有多少字符串以此字符结尾,这样就可以查加入了几次"be"
  3. 再扩充一个功能? 给你一个字符串,问有多少字符串以它为前缀?
    再加一个数据项: 表示每一个节点被滑过多少次.

前缀树的特点

加入查询的操作与数据量无关,只与被操作字符串长度有关.

前缀树的实现leetcode 208

前缀树节点结构: 前缀树的节点为26字母之一,因此不将其字母写在节点上,而是将子节点写进一个长度为26的数组,若数组对应index为空则说明无节点,否则存在该节点.

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

	public TrieNode() {
		path = 0;
		end = 0;
		nexts = new TrieNode[26];
	}
}

在实现前缀树时,我们并没有将字母值放在节点上,而是将其放在边上(数组或map),这样方便遍历的同时也节约了一个成员变量

public class TrieTree {
	private TrieNode root;	// 存储字典树根

	public TrieTree() {
		root = new TrieNode();
	}
	
	// 向前缀树中添加一个字符串
	public void insert(String word);

	// 在前缀树中查询一个字符串的出现次数
	public int search(String word);

	// 从前缀树中删除一个字符串
	public void delete(String word);

	// 查找以某字符串开头的长字符串数
	public int prefixNumber(String pre);
}

下面是具体方法的实现:

  1. insert(String word)方法: 沿着要加入的字符串一路查找下去,若子节点存在就滑过去,若不存在就建立对应索引的节点. 中途经过的节点的path++,遇到末尾节点,则end++.

    // 向前缀树中添加一个字符串
    public void insert(String word) {
    	if (word == null) {
    		return;
    	}
    	// 把字符串转化为字符数组
    	char[] chs = word.toCharArray();
    	TrieNode node = root;
    	// 从根节点沿着字符数组向下滑
    	for (char ch : chs) {
    		int index = ch - 'a';
    		// 若对应索引的节点不存在,则创建该节点
    		if (node.nexts[index] == null) {
    			node.nexts[index] = new TrieNode();
    		}
    		// 若对应索引的节点存在,则滑过该节点
    		// 先滑到下一个节点,然后再path++,下边两句话不能反了
    		node = node.nexts[index];
    		node.path++;
    	}
    	node.end++;
    }
    
  2. search(String word): 查询字符串出现次数,遍历方式与添加相同. 中途若有节点找不到则返回0,否则找到末尾节点并返回其end成员变量.

    // 在前缀树中查询一个字符串的出现次数
    public int search(String word) {
    	if (word == null) {
    		return 0;
    	}
    	char[] chs = word.toCharArray();
    	TrieNode node = root;
    	// 查询与添加一样,沿着字符数组一路滑下去
    	for (char ch : chs) {
    		int index = ch - 'a';
    		if (node.nexts[index] == null) {
    		// 若某中途节点不存在,则后边节点一定不存在,字符串出现次数为0
    			return 0;
    		}
    		node = node.nexts[index];
    	}
    	// 返回以末尾节点结束的字符串的个数
    	return node.end;
    }
    
  3. delete(String word)方法: 先查询该字符串是否存在,若不存在,则不用删.若字符串存在,则将沿途节点出现次数减一,若某节点被删除,其子节点必然被删除,因此可以直接返回,让jvm回收其子节点.

    // 从前缀树中删除一个字符串
    public void delete(String word) {
    	// 先查询字符串是否存在,若不存在则不用删除了
    	if (search(word) != 0) {
    		char[] chs = word.toCharArray();
    		TrieNode node = root;
    		// 遍历节点,将沿途节点出现次数减一
    		for (char ch : chs) {
    			int index = ch - 'a';
    			// 若中途某节点出现次数被减到0次,则其后节点也将不存在,直接返回即可
    			if (--node.nexts[index].path == 0) {
    				node.nexts[index] = null;
    				return;
    			}
    			node = node.nexts[index];
    		}
    		node.end--;
    	}
    }
    
  4. prefixNumber(String pre)方法:找到前缀的最后一个节点,返回其path成员变量值

    // 查找以某字符串开头的长字符串数
    public int prefixNumber(String pre) {
    	if (pre == null) {
    		return 0;
    	}
    	char[] chs = pre.toCharArray();
    	TrieNode node = root;
    	// 遍历找到前缀的最后一个节点
    	for (char ch : chs) {
    		int index = ch - 'a';
    		if (node.nexts[index] == null) {
    			return 0;
    		}
    		node = node.nexts[index];
    	}
    	// 返回前缀最后一个节点的path变量
    	return node.path;
    }
    

leetcode上的前缀树习题: leetcode 745,leetcode 14,字典树专题

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前缀树(Trie树)是一种用于字典查找的数据结构,它可以高效地完成字符串的插入、删除和查找操作。下面是前缀树的生成算法: 1. 定义一个Trie节点类,包含一个指向子节点的指针数组和一个布尔变量表示该节点是否为单词结尾。 ``` class TrieNode { public: TrieNode* children[26]; bool isEndOfWord; TrieNode() { for (int i = 0; i < 26; i++) children[i] = NULL; isEndOfWord = false; } }; ``` 2. 定义一个Trie类,包含一个根节点指针。在构造函数中初始化根节点。 ``` class Trie { public: TrieNode* root; Trie() { root = new TrieNode(); } }; ``` 3. 定义一个插入字符串的函数。从根节点开始,遍历待插入字符串的每个字符,如果当前字符对应的子节点不存在,则创建一个新的子节点,并将当前节点指向该子节点。最后将最后一个字符所对应的节点标记为单词结尾。 ``` void insert(string word) { TrieNode* node = root; for (char ch : word) { int index = ch - 'a'; if (node->children[index] == NULL) node->children[index] = new TrieNode(); node = node->children[index]; } node->isEndOfWord = true; } ``` 4. 定义一个查找字符串的函数。从根节点开始,遍历待查找字符串的每个字符,如果当前字符对应的子节点不存在,则返回false。如果查找完成后,最后一个字符所对应的节点为单词结尾,则返回true。 ``` bool search(string word) { TrieNode* node = root; for (char ch : word) { int index = ch - 'a'; if (node->children[index] == NULL) return false; node = node->children[index]; } return node != NULL && node->isEndOfWord; } ``` 5. 定义一个以某个前缀开头的所有单词的函数。从根节点开始,遍历前缀字符串的每个字符,如果当前字符对应的子节点不存在,则返回空vector。如果遍历完成后,最后一个字符所对应的节点存在,则从该节点开始深度优先遍历整个子树,将遇到的所有单词加入结果vector中。 ``` vector<string> startsWith(string prefix) { vector<string> result; TrieNode* node = root; for (char ch : prefix) { int index = ch - 'a'; if (node->children[index] == NULL) return result; node = node->children[index]; } collectWords(node, prefix, result); return result; } void collectWords(TrieNode* node, string prefix, vector<string>& result) { if (node->isEndOfWord) result.push_back(prefix); for (int i = 0; i < 26; i++) { if (node->children[i] != NULL) { char ch = 'a' + i; collectWords(node->children[i], prefix + ch, result); } } } ``` 这样,我们就完成了前缀树的生成算法

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值