字典树(f m)

日常一水题,字典树的基本构造。。。


啥都不用说了,直接上题
Problem Description
遇到单词不认识怎么办? 查字典啊,已知字典中有n个单词,假设单词都是由小写字母组成。现有m个不认识的单词,询问这m个单词是否出现在字典中。
Input
含有多组测试用例。
第一行输入n,m (n>=0&&n<=100000&&m>=0&&m<=100000)分别是字典中存在的n个单词和要查询的m个单词.
紧跟着n行,代表字典中存在的单词。
然后m行,要查询的m个单词
n=0&&m=0 程序结束
数据保证所有的单词都是有小写字母组成,并且长度不超过10
Output
若存在则输出Yes,不存在输出No .
Example Input

3 2
aab
aa
ad
ac
ad
0 0

Example Output

No
Yes

很典型的字典树,我们可以直接一个结构体,里面放上24个指针(因为就只有小写字母)。。。。。然后来一个转化为对应数字,放进去,当有许多个指针的链表做就好啦

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct node 
{
    int sum;
    struct node *next[30];
}a[1000000];
int top;
struct node *creat()
{
    struct node *p = &a[top++];
    p -> sum = 0;
    for(int i = 0;i<26;i++)
    {
        p -> next[i] = NULL;
    }
    return p;
}
void insert(struct node *root,char *str)
{
    struct node *p = root;
    for(int i = 0;str[i]!='\0';i++)
    {
        int t = str[i] - 'a';
        if(p -> next[t] == NULL)
            p -> next[t] = creat();
        p = p -> next[t];
    }
    p -> sum ++;
}
int find(struct node *root,char *str)
{
    struct node *p = root;
    for(int i = 0;str[i]!='\0';i++)
    {
        int t = str[i] - 'a';
        if(p -> next[t] == NULL)
            return 0;
        p = p -> next[t];
    }
    return p -> sum;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)
            break;
        top = 0;
        struct node *root = creat();//初始化一下。。。
        while(n--)
        {
            char str[100];
            scanf("%s",str);
            insert(root,str);
        }
        while(m--)
        {
            char str[100];
            scanf("%s",str);
            if(find(root,str))
                printf("Yes\n");
            else
                printf("No\n");
        }
    }
    return 0;
}

链表学好了这个也没啥问题。。。。。

一、综合实战—使用极轴追踪方式绘制信号灯 实战目标:利用对象捕捉追踪和极轴追踪功能创建信号灯图形 技术要点:结合两种追踪方式实现精确绘图,适用于工程制图中需要精确定位的场景 1. 切换至AutoCAD 操作步骤: 启动AutoCAD 2016软件 打开随书光盘中的素材文件 确认工作空间为"草图与注释"模式 2. 绘图设置 1)草图设置对话框 打开方式:通过"工具→绘图设置"菜单命令 功能定位:该对话框包含捕捉、追踪等核心绘图辅助功能设置 2)对象捕捉设置 关键配置: 启用对象捕捉(F3快捷键) 启用对象捕捉追踪(F11快捷键) 勾选端点、中心、圆心、象限点等常用捕捉模式 追踪原理:命令执行时悬停光标可显示追踪矢量,再次悬停可停止追踪 3)极轴追踪设置 参设置: 启用极轴追踪功能 设置角度增量为45度 确认后退出对话框 3. 绘制信号灯 1)绘制圆形 执行命令:"绘图→圆→圆心、半径"命令 绘制过程: 使用对象捕捉追踪定位矩形中心作为圆心 输入半径值30并按Enter确认 通过象限点捕捉确保圆形位置准确 2)绘制直线 操作要点: 选择"绘图→直线"命令 捕捉矩形上边中点作为起点 捕捉圆的上象限点作为终点 按Enter结束当前直线命令 重复技巧: 按Enter可重复最近使用的直线命令 通过圆心捕捉和极轴追踪绘制放射状直线 最终形成完整的信号灯指示图案 3)完成绘制 验证要点: 检查所有直线是否准确连接圆心和象限点 确认极轴追踪的45度增量是否体现 保存绘图文件(快捷键Ctrl+S)
<think>我们已知:用户希望将现有的联想词字典转换为纯Trie树结构。参考引用中提供了Trie树的基本概念和实现方法(引用[1]和引用[2]),以及Trie树的标准接口(引用[3])。目标:将联想词字典(可能是一个单词列表)转换为纯Trie树结构。步骤:1.理解Trie树结构:Trie树(前缀树)是一种树形据结构,用于高效存储和检索字符串集合。每个节点代表一个字符,从根节点到某一节点的路径表示一个字符串的前缀,叶子节点(或标记为结束的节点)表示完整的单词。2.联想词字典:假设用户有一个包含多个单词的列表(例如:['apple','app','application','banana']),我们需要将这些单词插入到Trie树中。3.构建Trie树的方法:-初始化一个空的根节点(通常是一个字典,或者一个包含children和is_end标记的节点对象)。-遍历每个单词的每个字符,从根节点开始,逐字符插入到树中。-对于每个字符,如果当前节点的children中存在该字符,则创建一个新的节点。-单词的最后一个字符被插入时,将该节点标记为结束(is_end=True)。4.具体实现(基于字典结构):我们可以用字典嵌套的方式来表示Trie树,每个节点是一个字典,key是字符,value是另一个字典(表示子节点)。同时,我们可以在字典中用一个特殊的key(如'is_end')来标记单词结束。5.代码示例:我们将实现一个Trie类,包含插入方法(将单词插入Trie树)以及搜索和前缀搜索方法(根据题目要求,但用户主要需要插入和构建树)。注意:用户要求的是“纯Trie树结构”,所以我们涉及知识图谱,只构建内存中的Trie树。实现参考引用[2]中的方法3(迭代法):-使用字典结构-每个节点是一个字典,包含:-子节点:键为字符,值为下一个节点的字典-结束标记:用一个特殊的键(如'is_end')来标记,值为布尔值。具体步骤:初始化:根节点为一个空字典。插入单词:遍历单词中的每个字符:如果当前字符在当前节点的子节点中,则新建一个子节点(即一个空字典)。否则,移动到子节点。在单词的最后一个字符所在的节点,设置结束标记为True。例如:插入"apple"根节点:{}插入'a':根节点下没有'a',创建新节点:{'a':{}},然后当前节点移到这个新节点。插入'p':当前节点(即'a'对应的节点)没有'p',创建新节点:{'p':{}},然后移到新节点。重复直到最后一个字符'e',在'e'对应的节点中设置结束标记:{'e':{'is_end':True}}注意:可能有多个单词共享前缀,比如插入"app"后,在第一个'p'的节点(即第三个节点)设置结束标记,同时这个节点还有子节点(因为还有"apple")。因此,每个节点需要同时存储:-子节点(用字典,键为字符,值为节点)-结束标记(可以单独用一个字段,但我们这里用字典,所以可以在节点字典中加一个键值对)但是,为了避免结束标记和子节点混淆,我们可以这样设计节点:节点={'children':{}#子节点字典,键为字符,值为节点(字典)'is_end':False#结束标记}或者,为了简洁,也可以将子节点和结束标记都放在同一个字典中,但用特殊的键(如'is_end')表示结束,其他键表示子节点。但这样在遍历子节点时需要排除这个特殊键。这里我们采用第一种方式,用两个独立的字段,这样更清晰。但是,引用[2]中采用的方法是将结束标记和子节点放在同一个字典中,用一个特殊的键(如'val')表示结束,而其他键都是子节点。但这样在查找子节点时,需要区分。我们参考LeetCode上常见的实现:每个节点使用一个字典(children)和一个布尔值(is_end)。但为了方便,我们这里用字典表示节点,但包含两个键:'children'和'is_end'。然而,为了节省空间,也可以单独设置'children'键,而是将子节点和结束标记都放在顶层。即:节点={'a':子节点1,'b':子节点2,...,'is_end':True#注意:这里'is_end'可能会和某个字符键冲突?但通常字符会命名为'is_end',所以可以。}但这样,在遍历子节点时,需要排除'is_end'。这种实现也是常见的。我们选择第二种方式(即节点字典中,字符键对应子节点,同时有一个'is_end'键表示结束)。注意:'is_end'键只在单词结束时才存在,或者我们可以初始化为False,但为了节省空间,我们可以在需要时才设置。但为了统一,我们可以在创建节点时都包含'is_end',并初始化为False,然后插入结束时设置为True。但这样每个节点都会多一个键。我们也可以初始化为False,而是通过判断该键是否存在,如果存在且为True则是结束,否则是。这里我们采用:节点就是一个字典,它可能有以下键:1.字符:指向子节点的字典。2.一个特殊的键,比如'#',表示结束标记(或者用'is_end')。当该键存在且为True时,表示这是一个单词的结束。但更常见的特殊键是使用一个非字符的标记,例如用'end'或者直接用一个布尔值字段。但为了简单,我们可以在节点中设置一个键,比如'is_end',当该键存在且为True时,表示结束。然而,引用[2]中的方法3描述为:字典的val也是字典,由“val”和其他子节点组成。他们用“val”=1表示结束。我们也可以这样。这里我们采用类似的方法:在节点中,用'is_end'键表示结束(值为True或False),其他键都是字符,对应子节点。具体实现:初始化:根节点为一个字典(包含一个'is_end'(值为False)和一个存放子节点的结构?)——实际上,根节点需要'is_end'(因为根节点代表任何字符,也作为单词结束),但我们可以统一每个节点都有'is_end',根节点的'is_end'始终为False。另一种方法:根节点就是一个空字典,然后我们在插入时,对于每个字符,在字典中查找,如果存在则创建一个新的字典(节点)。在单词结束时,我们在最后一个节点的字典中添加一个键值对:'is_end':True。我们采用这种方法,节点字典中:-除了字符作为键指向子节点外,还有一个可选的'is_end'键(当存在且为True时,表示该节点是单词结尾)。注意:这样在遍历时,如果我们要获取所有子节点,需要排除'is_end'键。但我们可以约定,节点字典中,所有非'is_end'的键都是字符,且指向子节点。实现Trie类(仅构建树,包含插入方法,需要搜索和前缀搜索,但为了完整,我们可以加上):步骤:1.初始化:根节点为一个空字典。2.插入单词:遍历单词的每个字符,从根节点开始,如果当前字符在当前节点的键中(注意:要排除'is_end'键?但实际上我们需要区分,因为字符键和'is_end'键会冲突,因为'is_end'键是固定的字符串,而字符是单个字符),所以我们可以直接判断字符是否在节点的键中(包括'is_end'?,我们应该把'is_end'当作一个字符)。因此,在检查字符时,我们只关心非'is_end'的键。但是,我们也可以这样:在节点中,'is_end'键和字符键是并存的。那么,在检查字符时,我们直接判断字符是否在该节点的键中(排除'is_end'),因为字符是一个字符,而'is_end'是一个字符串,它们类型相同?,都是字符串。所以可能会冲突:如果有一个字符恰好是字符串'is_end'?可能,因为字符是单个字符,而'is_end'是6个字符。所以会冲突。因此,我们可以直接判断字符是否在节点的键中。插入过程:current=rootforcinword:ifcnotincurrent:#如果当前节点没有这个字符的子节点current[c]={}#创建一个新的节点(空字典)current=current[c]#移动到子节点#循环结束后,current指向最后一个字符对应的节点current['is_end']=True#标记单词结束但是,这样标记的话,如果同一个单词插入两次,第二次只是将'is_end'设置为True(覆盖原有的True)。所以没问题。但是,如果我们希望支持多次插入同一个单词(并记录次),那么我们可以将'is_end'改为计。但题目没有要求,我们就用布尔值。注意:在插入过程中,我们会覆盖已有的路径,所以可以重复插入,但结束标记会覆盖。但是,用户的需求是构建联想词字典,所以单词应该是唯一的,因此需要计。因此,我们按上述方法构建。但是,在构建完成后,我们如何表示这个Trie树?实际上,整个Trie树就是由根节点(一个字典)以及通过字符键连接起来的多个字典节点组成。例如,插入"app"和"apple"后,树的结构大致如下:root={'a':{'p':{'p':{'is_end':True,#表示"app"结束'l':{'e':{'is_end':True#表示"apple"结束}}}}}}这样,我们就构建了一个Trie树。另外,我们可以实现一个类来封装,并包含搜索和前缀搜索方法,但用户问题只要求生成Trie树,所以我们可以只提供插入和树结构。但是,为了验证,我们可以提供完整的Trie类。根据引用[3]的接口要求,我们实现insert,search,startsWith方法。代码实现:注意:在搜索时,我们需要检查路径是否存在,并且对于search,最后一个节点必须有'is_end'且为True。具体实现:我们将创建一个Trie类,包含:__init__:初始化根节点(一个字典)insert:插入单词search:搜索单词是否存在startsWith:检查是否有前缀但是,用户只需要将字典转换为Trie树,所以重点在insert方法。代码示例:```pythonclassTrie:def__init__(self):self.root={}definsert(self,word):current=self.rootforcinword:ifcnotincurrent:current[c]={}current=current[c]#设置结束标志current['is_end']=Truedefsearch(self,word):current=self.rootforcinword:ifcnotincurrent:returnFalsecurrent=current[c]#检查是否有结束标志return'is_end'incurrentandcurrent['is_end']defstartsWith(self,prefix):current=self.rootforcinprefix:ifcnotincurrent:returnFalsecurrent=current[c]returnTrue```但是,上面的startsWith方法只要存在该前缀路径就返回True,即使它是一个完整的单词。注意:在插入时,我们需要在中间节点设置'is_end'(除非该节点也是一个单词的结尾)。例如,插入"app"后,在第二个'p'的节点设置'is_end'为True。再插入"apple"时,我们继续在第二个'p'的节点下添加'l',然后'e',并在'e'的节点设置'is_end'。此时,第二个'p'的节点仍然有'is_end'=True,表示"app"是一个单词。这样,我们的Trie树就支持了多个单词。现在,用户有一个联想词字典(一个单词列表),我们可以这样构建:trie=Trie()forwordinword_list:trie.insert(word)构建完成后,trie.root就是整个Trie树的根节点。但是,如果用户需要封装成类,也可以直接构建一个字典结构,然后写一个插入函:```pythondefbuild_trie(words):root={}forwordinwords:current=rootforcinword:ifcnotincurrent:current[c]={}current=current[c]current['is_end']=Truereturnroot```这样,返回的root就是Trie树的根节点。注意:这种方法中,每个节点都是一个字典,节点之间通过字符键链接,在单词结束的节点设置'is_end'标志。因此,我们可以根据用户的输入(单词列表)构建出Trie树。最后,用户可能需要将这个Trie树持久化保存,但问题没有要求,所以暂时需要。总结步骤:1.准备联想词列表(例如:['apple','app','banana'])2.初始化一个空字典作为根节点。3.遍历每个单词,将单词的每个字符依次插入到树中,并在单词结尾的节点添加'is_end'=True。这样,就生成了纯Trie树结构。注意:这种方法在节点中使用了字典,所以是纯内存结构,没有使用类(也可以使用类,但用字典更基础)。我们提供两种方式:函方式和类方式,让用户选择。相关问题:1.如何优化Trie树的内存使用?2.Trie树和哈希表在单词查找上有什么优缺点?3.如何将Trie树持久化存储到文件中?4.如何实现Trie树的删除操作?5.在Trie树中如何支持中文联想词?下面我们按照要求生成回答。</think>### 将联想词字典转换为纯Trie树结构的方法 #### 一、基础转换流程 将现有联想词字典转换为纯Trie树的核心步骤: 1. **初始化根节点** ```python class TrieNode: def __init__(self): self.children = {} # 子节点字典 self.is_end = False # 单词结束标志 self.freq = 0 # 词频统计(可选) class Trie: def __init__(self): self.root = TrieNode() # 创建空根节点 ``` 2. **字典据插入算法** ```python def insert(self, word: str, freq=1) -> None: node = self.root for char in word: if char not in node.children: node.children[char] = TrieNode() # 创建新节点 node = node.children[char] # 移动到子节点 node.is_end = True # 标记单词结束 node.freq += freq # 更新词频(可选) ``` 3. **批量转换方法** ```python def build_from_dict(word_dict: dict) -> Trie: trie = Trie() for word, freq in word_dict.items(): # 假设字典格式{单词: 词频} trie.insert(word, freq) return trie ``` #### 二、据结构映射原理 | 字典结构 | Trie树映射 | 示例说明 | |------------------|--------------------|----------------------------| | 单词"apple" | 根→a→p→p→l→e | 字符序列路径[^1] | | 词频统计 | 结束节点freq属性 | 叶子节点存储附加据[^2] | | 前缀共享 | 节点复用 | "app"共享"apple"的前三个节点 | | 联想词关系 | 分支结构 | "app"和"apple"形成子树 | #### 三、性能优化技巧 1. **内存压缩** ```python # 使用组替代字典(适用于固定字符集) self.children = [None] * 26 # 仅支持小写字母 # 计算字符索引 index = ord(char) - ord('a') ``` 2. **懒删除机制** ```python def delete(self, word: str) -> bool: node = self.root stack = [] # 记录路径节点 for char in word: if char not in node.children: return False stack.append((node, char)) # 保存节点和字符 node = node.children[char] if not node.is_end: return False node.is_end = False # 仅标记删除而非物理移除 return True ``` 3. **序列化存储** ```python def serialize(node): if not node.children: return str(int(node.is_end)) return "{" + ",".join( f"{char}:{serialize(child)}" for char, child in node.children.items() ) + "}" + str(int(node.is_end)) ``` #### 四、完整转换示例 ```python # 输入字典 word_dict = { "apple": 25, "app": 30, "application": 12, "banana": 18 } # 构建Trie trie = Trie() for word, freq in word_dict.items(): trie.insert(word, freq) # 结构可视化 """ 根节点 ├─ a → p → p (is_end=True, freq=30) │ └─ l → e (is_end=True, freq=25) │ └─ i → c → a → t → i → o → n (is_end=True, freq=12) └─ b → a → n → a → n → a (is_end=True, freq=18) """ ``` #### 五、应用场景对比 | **操作** | 字典结构(HashMap) | Trie树 | 优势说明 | |------------------|------------------|---------------|----------------------| | 前缀匹配 | O(n)全表扫描 | O(m)路径查找 | m为前缀长度[^3] | | 空间占用 | O(n) | O(n×k) | k为平均字符 | | 按字母序遍历 | 需要排序 O(n log n)| O(n)自然有序 | 深度优先遍历即有序 | | 相似词推荐 | 支持 | 支持子树遍历 | 自动完成功能基础[^1] | > **实际应用**:当输入"app"时,Trie树可在O(3)时间内返回: > `["app", "apple", "application"]`(通过遍历p节点的子树) #### 六、进阶优化方向 1. **双组Trie**:压缩状态转移表,减少60%内存占用 2. **后缀树优化**:合并重复后缀,适用于长尾词库 3. **AC自动机**:在Trie上添加失败指针,支持多模式匹配 4. **持久化存储**:使用mmap内存映射加载10GB+词库 ```c // 双组Trie核心结构(C语言) typedef struct { int *base; int *check; char *tail; } DoubleArrayTrie; ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值