208实现 Trie (前缀树)(字典树)

1、题目描述

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

说明:

你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

2、示例

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

3、题解

解法一:字典树

基本思想:字典树(前缀树)的设计与实现,建议多看看高级数据结构源码实现
给每一个字符建立一个Trie结构,当前字符的后继字符可能是任意26个字母所以有26个孩子,如果截止当前字符构成插入的字符串,置当前字符节点的isWord为true。
Trie 树是一个有根的树,其结点具有以下字段:

  • 最多R个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。本题R为26
  • 布尔字段,以指定节点是对应键的结尾还是只是键前缀。

解法二:

基本思想:HashMap+哈希表,用HashMap保存所有的前缀映射,比如apple:a->ap ap->app app->appl appl->apple。dict保存所有插入的字符串,使用HashMap和dict查找是否存在前缀prefix,插入一个字符串,建立相应的前缀映射,从后往前映射appl->apple到a->ap,这样对于相同前缀的appll字符插入时只需要插入appl->appll,效率提升。
但是这种方法最终效率还是很低,因为随着哈希表大小增加,会出现大量的冲突。Trie 树优于哈希表的理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到O(n),其中n 是插入的键的数量。与哈希表相比,Trie树在存储多个具有相同前缀的键时可以使用较少的空间。此时Trie树只需要O(m) 的时间复杂度,其中m为键长。

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<unordered_set>
using namespace std;
class Trie {
public:
	/** Initialize your data structure here. */
	Trie() {
		//基本思想:字典树(前缀树)的设计与实现,建议多看看高级数据结构源码实现
		//给每一个字符建立一个Trie结构,当前字符的后继字符可能是任意26个字母所以有26个孩子
		//如果截止当前字符构成插入的字符串,置当前字符节点的isWord为true
		//Trie 树是一个有根的树,其结点具有以下字段:
		//最多R个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。本题R为26
		//布尔字段,以指定节点是对应键的结尾还是只是键前缀。
		for (int i = 0; i < 26; i++)
			child[i] = nullptr;
		isWord = false;
	}

	/** Inserts a word into the trie. */
	void insert(string word) {
		Trie* t = this;
		for (auto c : word)
		{
			if (t->child[c - 'a'] == nullptr)
				t->child[c - 'a'] = new Trie();
			t = t->child[c - 'a'];
		}
		t->isWord = true;
	}

	/** Returns if the word is in the trie. */
	bool search(string word) {
		Trie* t = this;
		for (auto c : word)
		{
			if (t->child[c - 'a'] == nullptr)
				return false;
			t = t->child[c - 'a'];
		}
		return t->isWord;
	}

	/** Returns if there is any word in the trie that starts with the given prefix. */
	bool startsWith(string prefix) {
		Trie* t = this;
		for (auto c : prefix)
		{
			if (t->child[c - 'a'] == nullptr)
				return false;
			t = t->child[c - 'a'];
		}
		return true;
	}
private:
	Trie* child[26];
	bool isWord;
};
class Trie1 {
public:
	/** Initialize your data structure here. */
	Trie1() {
		//基本思想:HashMap+哈希表,用HashMap保存所有的前缀映射,比如apple:a->ap ap->app app->appl appl->apple
		//dict保存所有插入的字符串,使用HashMap和dict查找是否存在前缀prefix,插入一个字符串,建立相应的前缀映射
		//从后往前映射appl->apple到a->ap,这样对于相同前缀的appll字符插入时只需要插入appl->appll,效率提升
		//但是这种方法最终效率还是很低,因为随着哈希表大小增加,会出现大量的冲突
		//Trie 树优于哈希表的理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到O(n),其中n 是插入的键的数量。
		//与哈希表相比,Trie树在存储多个具有相同前缀的键时可以使用较少的空间。此时Trie树只需要O(m) 的时间复杂度,其中m为键长。
	}
	/** Inserts a word into the trie. */
	void insert(string word) {
		if (dict.find(word) != dict.end())
			return;
		dict.insert(word);
		string pre, cur = word;
		for (int i = word.size() - 1; i >= 1; i--)
		{
			pre = word.substr(0, i);
			if (HashMap.find(pre) == HashMap.end() || HashMap.find(cur) == HashMap.end())
				HashMap.insert({ pre,cur });
			else
				return;
			cur = pre;
		}
		pre = "*";
		HashMap.insert({ pre,cur });
	}

	/** Returns if the word is in the trie. */
	bool search(string word) {
		if (dict.find(word) != dict.end())
			return true;
		else
			return false;
	}

	/** Returns if there is any word in the trie that starts with the given prefix. */
	bool startsWith(string prefix) {
		if (dict.find(prefix) != dict.end())
			return true;
		if (HashMap.find(prefix) != HashMap.end())
			return true;
		return false;
	}
private:
	multimap<string, string> HashMap;
	unordered_set<string> dict;
};
int main()
{
	Trie trie;
	trie.insert("apple");
	cout << trie.search("apple") << endl;   // 返回 true
	cout << trie.search("app") << endl;     // 返回 false
	cout << trie.startsWith("app") << endl; // 返回 true
	trie.insert("app");
	cout << trie.search("app") << endl;     // 返回 true
	return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值