原题目链接:
P1481 魔族密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
原题目截图:
思路分析:
这道题的话其实有很多种方法,可以用动态规划做,不过我一看到这道题,脑子里不禁蹦出一个数据结构:“字典树”!
字典树+深度优先搜索。
那么在这之前,我们先来了解一下什么是字典树吧!
什么是字典树:
字典树(Trie),也称为前缀树或Ternary Search Tree,是一种用于快速检索字符串数据集中的键的数据结构。它的优点在于可以快速地插入和查找字符串,并且可以有效地用于实现自动补全和拼写检查等功能。
字典树的特点:(有颜色的部分是重点)
-
共同前缀:字典树利用字符串的共同前缀来减少存储空间和提高查找效率。
-
节点:字典树由多个节点组成,每个节点表示一个字符。
-
边:节点之间的连接表示从父节点到子节点的字符。
-
根节点:字典树的根节点通常表示一个空字符串。
-
结束符:通常用一个标志(如
isEndOfWord
)来表示某个节点是一个单词的结束。
字典树的工作原理:
-
插入:从根节点开始,对要插入的字符串的每个字符,检查是否存在相应的子节点。如果不存在,则创建一个新的节点。最后,标记该字符串的结束节点。
-
查找:从根节点开始,按照要查找的字符串的字符顺序遍历节点。如果某个字符的子节点不存在,则表示该字符串不在字典树中。
-
删除:从根节点开始,按照要删除的字符串的字符顺序遍历节点。对于每个节点,检查是否有其他子节点。如果没有,则可以删除该节点。
字典树的应用场景:
-
自动补全:在用户输入时,根据已输入的字符串提示可能的完整单词。
-
拼写检查:检查用户输入的单词是否拼写正确。
-
IP 路由:在网络路由中,IP地址可以通过字典树快速匹配到相应的路由策略。
-
文本搜索:实现文本搜索算法,如字符串搜索和模式匹配。
怎么构建字典树:
我们知道了,字典树其实就是把字符串统计在一个树状结构里面,每个节点储存一个字符。
那么我们以题目所给的案例,来试着构建一颗字典树吧:
剩下的插入操作大同小异,我们这里直接看最终结果:
构建字典树的代码实现:
这道题的话,其实只涉及到插入操作。
我们先看看字典树应该有一个怎样的节点结构:
#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;
// 定义字典树的节点
struct trienode {
char val; // 节点代表的字符
unordered_map<char, trienode*> ump; // 存储子节点的哈希表,键是字符,值是指向该字符对应节点的指针
bool iswordend; // 标记该节点是否为一个单词的结束位置
// 构造函数,初始化节点的值和结束标志
trienode(char v) : val(v), iswordend(false) {}
// 插入单词到字典树
void insertword(string word) {
trienode* cur = this; // 从当前节点(根节点或父节点)开始
for (char ch : word) { // 遍历单词中的每个字符
if (!cur->ump.count(ch)) { // 如果当前节点没有该字符的孩子节点
cur->ump[ch] = new trienode(ch); // 创建一个新的节点并添加到哈希表中
}
cur = cur->ump[ch]; // 移动到下一个节点
}
cur->iswordend = true; // 将单词的最后一个字符对应的节点标记为单词结束
}
};
代码解释:
- 结构体定义:
trienode
是字典树的节点,包含一个字符val
,一个哈希表ump
用于存储子节点,以及一个布尔值iswordend
用于标记单词的结束。 - 构造函数:初始化节点的字符和结束标志。结束标志默认为
false
。 - insertword函数:
- 参数:
word
是需要插入到字典树中的单词。 - 过程:
- 使用一个指针
cur
从当前节点开始遍历。 - 对于单词中的每个字符,检查当前节点是否有该字符的子节点。
- 如果没有,创建一个新的节点并添加到哈希表中。
- 将
cur
更新为该子节点,继续遍历下一个字符。 - 遍历完成后,将单词的最后一个字符对应的节点标记为单词结束。
- 使用一个指针
- 参数:
解决代码:
当你把字典树建好之后,你会发现这其实就是一道很简单的深度优先计数题了:
#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;
// 定义字典树的节点
struct trienode {
char val; // 节点代表的字符
unordered_map<char, trienode*> ump; // 存储子节点的哈希表,键是字符,值是指向该字符对应节点的指针
bool iswordend; // 标记该节点是否为一个单词的结束位置
trienode(char v) : val(v), iswordend(false) {} // 构造函数
// 插入单词到字典树
void insertword(string word) {
trienode* cur = this; // 从当前节点(根节点或父节点)开始
for (char ch : word) { // 遍历单词中的每个字符
if (!cur->ump.count(ch)) { // 如果当前节点没有该字符的孩子节点
cur->ump[ch] = new trienode(ch); // 创建一个新的节点并添加到哈希表中
}
cur = cur->ump[ch]; // 移动到下一个节点
}
cur->iswordend = true; // 将单词的最后一个字符对应的节点标记为单词结束
}
};
// 深度优先搜索(DFS)函数,用于找到最长的词链
void dfs(trienode* root, int ¤tLength, int& maxn) {
if (!root) return; // 如果当前节点为空,则返回
if (root->ump.empty()) {
// 如果当前节点没有子节点,说明到达了叶子节点,即一条链路的终点
maxn = max(maxn, currentLength); // 更新最长链长度
return;
}
for (auto& it : root->ump) {
// 遍历当前节点的所有子节点
if (it.second->iswordend) {
// 如果子节点是单词的结尾,则增加当前链的长度
currentLength++;
}
// 递归地对每个子节点进行深度优先搜索
dfs(it.second, currentLength, maxn);
if (it.second->iswordend) {
// 如果子节点是单词的结尾,在回溯时减少当前链的长度
currentLength--;
}
}
}
int main() {
int n;
cin >> n;
trienode* root = new trienode('*'); // 头结点
for (int i = 0; i < n; i++) {
string word;
cin >> word;
root->insertword(word);
}
int maxn = 0;
int c = 0;
dfs(root, c, maxn); // 进行DFS搜索
cout << maxn << endl; // 输出最长链长度
// 释放内存
// 注意:实际应用中需要递归释放字典树的所有节点内存
return 0;
}
对于这里dfs的写法,开始我没有留意到符号&,在出错之后,我把这个小细节注意到了。
各位读者不妨比较一下上下两段代码中dfs函数的细微区别。
当然这里给出的两段代码都是正确的可运行代码。
#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;
// 定义字典树的节点
struct trienode {
char val;
unordered_map<char, trienode*> ump;
bool iswordend;
trienode(char v) : val(v), iswordend(false) {}
// 插入
void insertword(string word) {
trienode* cur = this;
for (char ch : word) {
if (!cur->ump.count(ch)) {
cur->ump[ch] = new trienode(ch);
}
cur = cur->ump[ch];
}
cur->iswordend = true;
}
};
void dfs(trienode* root, int currentLength, int& maxn) {//currentLenth表示当前递归已有单词数
if (!root) return;
if (root->ump.empty()) {
maxn = max(maxn, currentLength);//到叶子结点,即一条链路完
return;
}
for (auto& it : root->ump) {
if (it.second->iswordend) currentLength++;
// cout << "dfs(" << it.second->val << "," << currentLength << "," << maxn << ")" << endl;
dfs(it.second, currentLength , maxn);
}
}
int main() {
int n;
cin >> n;
trienode* root = new trienode('*'); // 头结点
for (int i = 0; i < n; i++) {
string word;
cin >> word;
root->insertword(word);
}
int maxn = 0;
int c = 0;
dfs(root, c, maxn); // 进行DFS搜索
cout << maxn << endl; // 输出最长链长度
return 0;
}
今天的博客就写到这里了,很快就要国庆假期了,预祝诸君国庆快乐!
标签:字符,P1481,洛谷,cur,trienode,魔族,ump,节点,字典 From: https://blog.csdn.net/weixin_70448721/article/details/142620366