【Trie】 (Prefix Tree) - 前缀树、字典树

LeetCode - 208. Implement Trie (Prefix Tree)
LeetCode - 648. Replace Words
  Trie, 又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
  Trie 这个术语来自于 retrieval。如下图,键标注在节点中,值标注在节点之下。每一个完整的英文单词对应一个特定的整数。Trie 可以看作是一个确定有限状态自动机,尽管边上的符号一般是隐含在分支的顺序中的。键不需要被显式地保存在节点中。图示中标注出完整的单词,只是为了演示trie的原理。

  trie 中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,bitwise trie 中的键是一串比特,可以用于表示整数或者内存地址。
  LeetCode 的 solution 中讲了 Trie 的几种常见用途:
1、自动补全 - Autocomplete

2、拼写检查 - Spell checker

3、IP路由 - IP routing (Longest prefix matching)

4、9键键盘 - T9 predictive text

  对 Trie 来说最常见的两个操作是 search for a keyinsert a key 。实现 Trie,其实不难,每个节点除了存一个 26 个大小的自身类型的节点外,还需要存一个 isEndofWord 这种标志位,因为 good 和 goodness 两个词如果都有,没有标志位就不知道有 good 这个词了,所以 g、o、o、n、e、s 的 isEndofWord 都是 false,d 和最后一个 s 的 isEndofWord 是 true,这样就可以知道有 good 这个词了。所以其实 search 和 startsWith 是一样的,唯一的区别就是最后是不是结尾了。insert 也很简单,就是有这个字符的子节点,就往下,没有就创造一个,再往下。(下边的代码没有进行析构,不过AC了)

struct TrieNode {
    TrieNode* children[26];
    bool isEndofWord;
    
    TrieNode() : isEndofWord(false) {
        for(int i = 0; i < 26; ++i)
            children[i] = nullptr;
    };
};

class Trie {
public:
    TrieNode* root;
    
    Trie() {
        root = new TrieNode();
    }
    
    void insert(string word) {
        auto rt = root;
        for(const auto& c : word) {
            const int num = c - 'a';
            if(rt->children[num] == nullptr)
                rt->children[num] = new TrieNode();
            rt = rt->children[num];                
        }
        rt->isEndofWord = true;
    }
    
    bool search(string word) {
        auto rt = root;
        int i = 0;
        while(i < word.length() && rt->children[word[i] - 'a'] != nullptr) {
            rt = rt->children[word[i] - 'a'];
            ++i;
        }
        return i == word.length() && rt->isEndofWord;
    }
    
    bool startsWith(string prefix) {
        auto rt = root;
        int i = 0;
        while(i < prefix.length() && rt->children[prefix[i] - 'a'] != nullptr) {
            rt = rt->children[prefix[i] - 'a'];
            ++i;
        }
        return i == prefix.length();
    }
};

LeetCode - 648. Replace Words
  这道题如下,就是用最短前缀替换sentence中的单词,可以用 prefix tree 来实现:

Given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length.
You need to output the sentence after the replacement.

Example 1:
Input: dict = [“cat”, “bat”, “rat”] sentence = “the cattle was rattled by the battery”
Output: “the cat was rat by the bat”

struct TrieNode {
	bool isEndofWord;
	TrieNode * children[26]{};

	TrieNode() : isEndofWord(false) {
		for (auto& c : children)
			c = nullptr;
	}
};

struct Trie {
	TrieNode* root;

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

	void insert(const string& s) {
		auto r = root;
		for (const char& c : s)	{
			if (r->children[c - 'a'] == nullptr)
				r->children[c - 'a'] = new TrieNode();
			r = r->children[c - 'a'];
		}
		r->isEndofWord = true;
	}

	string findShortestPrefix(const string& s) {
		auto r = root;
		for(int i = 0; i < s.length(); ++i)
		{
			if (r->children[s[i] - 'a'] == nullptr)
				return s;
			r = r->children[s[i] - 'a'];
			// 找到一个prefix就返回,肯定是最短的
			if (r->isEndofWord)
				return s.substr(0, i + 1);
		}
		return s;
	}
};

string replaceWords(const vector<string>& dict, string sentence) {
	Trie tri;
	for (const auto& word : dict)
		tri.insert(word);

	bool first = true;
	string res, sen;
	stringstream ss;
	ss << sentence;
	while (ss >> sen) {
		if (first) {
			res = tri.findShortestPrefix(sen);
			first = false;
		}
		else
			res += " " + tri.findShortestPrefix(sen);
	}
	return res;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值