字典树的基础,以及在实际项目中对于敏感词的替换的应用

最近刷题时连续遇到两道字典树的题目,所以做一下这个数据结构的总结。

首先什么叫做字典树?

我   想

这种树结构并且把文字或者英文放在里面组成的叫做字典树。

那么字典树有什么用呢?

通过几道题目的练习我发现,字典树主要应用在,对于字符串的分级匹配和查询。

比如在我们如果有三句话,1:我是人,2:我是男人,3:我是中国人

如果一般的我们用三个字符串去存放他们,然后当我们要寻找在这些字符串中是否存在我是中国人的时候,那么就需要一句句匹配过来,如果有1000条这样的数据,那么匹配的速度可想而知。

当我们用字典树去存放的时候,我们可以存放成这样

我  是   男   人

中   国   人

这样我们从树的根部一直寻找下去,一个个字去寻找,如果有,那么就找下去,如果没有就不存在,这样寻找速度也是可想而知的。

然后下面是字典树在C中的定义,以及简单的应用

typedef struct Node
{
    int number;
    char word[120];
    Node *next[26];
    void init()
    {
        number = 0;
        memset(next,NULL,sizeof(next));
    }
}NODE,*PNODE;

当然一般的题目都是英文的,所以存放的是英文的字符串,所以其中的next用了26长度的数组

PNODE setNode()
{
    PNODE pNew = (PNODE) malloc(sizeof(NODE));
    pNew->init();
    return pNew;
}

创建一个新的节点

void insertNode(char st[120], int number)
{
    char temp[120];
    PNODE pNew = pHead;
    int length = strlen(st);
    int i,j;
    for (i = 0; i < length; i++)
    {
        j = st[i] - ‘a‘;
        if(pNew->next[j] == NULL)
        {
            pNew->next[j] = setNode();
        }
        pNew = pNew->next[j];
        pNew->number += number;
        temp[i] = st[i];/*把每一段到这个节点为止的字符串赋值,并在最后赋值、0否则打印时会出错*/
        temp[i+1] = ‘\0‘;
        strcpy(pNew->word , temp);
    }
}

插入新的节点(简单的描述一下,就是先把根节点找到,然后去要加入字典树的字符串的第一个字符,减去‘a’使它变成数字,然后寻找根节点下面是否存在对应数字的节点,没有就新建一个节点,有就在新的节点上面赋值,以此类推)

最后顺便说一句的就是,我虽然掌握了字典树的构造,却没有办法解题的原因是,字典树只是数据结构,而真正题目往往需要dfs或者dp加以运用和搜索才能得到结果,所以之后还是要对搜索上面的算法加以掌握。

还有就是,在实际项目中可以运用字典树和DFA算法实现对于敏感词的替换,因为脏活数据库本来数据量就及其庞大,如果没有好的数据结构和算法的话,对于脏活的替换会非常的耗时耗力。下面是替换的实际中可以使用的工具类(一个是数据结构,一个是算法部分,两者都要导入)。

package dfaWord;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 脏话匹配替换工具类(数据结构部分)
 * @author XEX
 *
 */
public class DFAwordInit {

    @SuppressWarnings("rawtypes")
    private HashMap wordsMap;

    /**
     * 初始化数据结构
     * @param dirtyWordsSet 脏话的数据集合(要把收到的json数组转换成集合的形式)
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void DFAwordMapInit(Set<String> dirtyWordsSet)
    {
        wordsMap = new HashMap(dirtyWordsSet.size());//根据脏话的数据集合大小来动态创建,减小空间复杂度

        Iterator<String> dirtyWordsIterator = dirtyWordsSet.iterator();//生成一个迭代器,用于循环取出集合中的数据

        String wordKeyTemp = null;
        Map wordsMapTemp_old = null;//当前的树的节点,或者是头节点
        Map<String, String> wordsMapTemp_new = null;//如果有新的词,生成的新的节点

        while (dirtyWordsIterator.hasNext()) {
            wordKeyTemp = dirtyWordsIterator.next();//获取字符串
            wordsMapTemp_old = wordsMap;//头结点

            for(int i=0;i<wordKeyTemp.length();i++)//循环每一个字
            {
                char charTemp =  wordKeyTemp.charAt(i);//取出一个字

                Object wordsMapTemp = wordsMapTemp_old.get(charTemp);//查询当前各个子节点是否存在这个字
                if(wordsMapTemp != null)//存在节点
                {
                    wordsMapTemp_old = (Map) wordsMapTemp;//存在节点的话就把当前节点指针指向存在的那个节点(表示这个字在这个数据结构中了)
                }
                else
                {
                    wordsMapTemp_new = new HashMap<String, String>();//如果不存在的话就新建一个节点(表示这个字在这个数据结构中还不存在)
                    wordsMapTemp_new.put("EoF", "0");//利用这个标识是否是最终节点
                    wordsMapTemp_old.put(charTemp, wordsMapTemp_new);//存放新节点
                    wordsMapTemp_old = wordsMapTemp_new;//把当前节点指针指向存在的那个新节点
                }
                if(i == wordKeyTemp.length() - 1){//如果字符串没了,那么把当前节点的标识的值变成1,表明这个已经是最终节点了
                    wordsMapTemp_old.put("EoF", "1");
                }
            }
        }
    }

    /**
     * 初始化方法
     * @param dirtyWordsSet 脏话数据集合(要把收到的json数组转换成集合的形式)
     * @return 脏话数据结构
     */
    @SuppressWarnings("rawtypes")
    public Map init(Set<String> dirtyWordsSet)
    {
        DFAwordMapInit(dirtyWordsSet);
        return wordsMap;
    }
}
package dfaWord;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.RandomStringUtils;

/**
 * 脏话匹配替换工具类(算法部分)
 * @author XEX
 *
 */
public class DFAwordFilter {

    /**
     * 测试用例(运行此方法即可看见效果,在项目别的地方之间按照本方法操作即可)
     */
    public static void main(String[] args) throws Exception
    {
        long beginTime = System.currentTimeMillis();

        Set<String> set = new HashSet<String>();//把数据放入集合中,下面是我随便放置的数据

        set.add("中国人民");//这个是敏感词
        for(int i=0;i<1000;i++)//这个是随机的数据,模拟脏活数据库的庞大
        {
            set.add("我"+RandomStringUtils.randomAlphanumeric(10)+"呀呀呀呀呀呀呀呀呀呀");
        }

        DFAwordFilter a = new DFAwordFilter(set);//构造数据结构

        System.out.println(a.wordsFilter("啊啊啊啊啊啊中国人民啊啊啊啊啊啊    啊啊啊", 1));//通过主方法替换字符串

        long endTime = System.currentTimeMillis();

        System.out.println(endTime - beginTime);
    }
    /**
     * 用来存放脏话数据结构
     */
    @SuppressWarnings("rawtypes")
    private Map wordsMap;

    /**
     * 构造方法
     * @param dirtyWordsSet 脏话数据集合(要把收到的json数组转换成集合的形式)
     */
    public DFAwordFilter(Set<String> dirtyWordsSet){
        wordsMap = new DFAwordInit().init(dirtyWordsSet);//初始化数据结构
    }

    /**
     * 替换字符串主方法
     * @param words 要进行替换的字符串
     * @param index 从这个位置开始替换
     * @return 替换后的字符串
     */
    public String wordsFilter(String words, int index)
    {
        Set<String> set = getDirtyWord(words);//获取这个字符串的所有脏字
        String resultWords = words;//用来保存替换后的字符串
        Iterator<String> iterator = set.iterator();//创建一个迭代器
        String wordTemp = null;
        while(iterator.hasNext())//循环这个字符串的所有脏字的集合
        {
            wordTemp = iterator.next();
            resultWords = resultWords.replaceAll(wordTemp, "*");//替换原字符串中的脏字为“*”
        }
        return resultWords;//返回替换后的字符串
    }

    /**
     * 获取用户输入字符串中的所有带的脏字
     * @param words 输入字符串
     * @return 所有带的脏字集合
     */
    public Set<String> getDirtyWord(String words)
    {
        Set<String> resultSet = new HashSet<String>();

        for(int i = 0; i < words.length(); i++)//循环这个字符串的每一个字
        {
            int resultLength = lengthOfWordCheck(words,i);//把这个字所在的位置传入,如果这个字和后面的字一起为脏话的话,返回这个脏话的长度

            if(resultLength > 0)//如果脏话长度大于0,则存在脏话
            {
                resultSet.add(words.substring(i, i + resultLength));//在结果集中加入这个脏话,通过原始字符串截断得到
                i += resultLength;//因为后面“resultLength”的长度已经是脏话了,跳过这脏话,继续检查后面的
                i--;
            }
        }
        return resultSet;//返回所有带的脏字集合
    }

    /**
     * 判断一个字符串是不是脏话
     * @param words 字符串
     * @param index 开始位置
     * @return 返回脏话长度
     */
    @SuppressWarnings("rawtypes")
    public int lengthOfWordCheck(String words,int index)
    {
        boolean flag = false;//立一个flag,如果是脏话那么true,不是为false
        int resultFlag = 0;//返回的脏话长度
        char wordTemp;
        Map wordsMap_old = wordsMap;

        for(int i=index; i<words.length(); i++)//循环这个字符串中的所有的字
        {
            wordTemp = words.charAt(i);//取出一个字
            wordsMap_old = (Map)wordsMap_old.get(wordTemp);//从当前子节点中查询是否存在脏字map中

            if(wordsMap_old != null)//如果存在
            {
                resultFlag++;//脏话长度加1
                if(wordsMap_old.get("EoF").equals("1"))//如果当前节点已经为最后一个节点的话
                {
                    flag = true;//存在脏话
                    break;//跳出循环。这个之后可以做修改,如果优先匹配长的字符串就把这个break删除,继续比较
                }
            }
            else
            {
                break;//不存在脏字
            }
        }

        if(!flag)
        {
            return 0;//不存在脏字返回脏字长度为0
        }
        else
        {
            return resultFlag;//存在脏字返回脏字长度
        }
    }
}
时间: 2024-10-12 13:41:07

字典树的基础,以及在实际项目中对于敏感词的替换的应用的相关文章

Trie(字典树)解析及其在编程竞赛中的典型应用举例

摘要: 本文主要讲解了Trie的基本思想和原理,实现了几种常见的Trie构造方法,着重讲解Trie在编程竞赛中的一些典型应用. 什么是Trie? 如何构建一个Trie? Trie在编程竞赛中的典型应用有些? 例题解析 什么是Trie? 术语取自retrieval中(检索,收回,挽回)的trie,读作"try",也叫做前缀树或者字典树,是一种有序的树形数据结构.我们常用字典树来保存字符串集合(但不仅限于字符串),如下图就是一个字典树. 它保存的字符集合是{to,te,tea,ted,te

LA_3942 LA_4670 从字典树到AC自动机

首先看第一题,一道DP+字典树的题目,具体中文题意和题解见训练指南209页. 初看这题模型还很难想,看过蓝书提示之后发现,这实际上是一个标准DP题目:通过数组来储存后缀节点的出现次数.也就是用一颗字典树从后往前搜一发.最开始觉得这种搞法怕不是要炸时间,当时算成了O(N*N)毕竟1e5的数据不搞直接上N*N的大暴力...后来发现,字典树根本跑不完N因为题目限制字典树最多右100层左右. 实际上这道题旧思想和模型来说很好(因为直观地想半天还真想不出来..)但是实际实现起来很简单--撸一发字典树就好了

1123: 统计难题 (字典树)

1123: 统计难题 时间限制: 1 Sec  内存限制: 128 MB 提交: 4  解决: 4 [提交][状态][讨论版] 题目描述 Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). 输入 输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个

字典树与01字典树

之前在做一道关于字符串匹配的题时,用到了字典树,但那时是用指针实现的,这次又遇到需要使用字典树这一结构的题,向学姐要了她的板子,学习了用数组实现的方法,对于解题而言,更为简短快速. 因为题目要求最大异或和,因此用的是01字典树,在字典树的基础上稍作修改. 以下为字典树和01字典树的普遍实现: 字典树 #include<iostream> #include<algorithm> using namespace std; struct Trie { static const int N

hdu 1247 Hat’s Words 字典树

// hdu 1247 Hat's Words 字典树 // // 题目大意: // // 在一些字符串中,找到这样字符串:由两个其他的字符串构成 // // 解题思路: // // 字典树,先将这些字符串插入到字典树中,然后枚举断点,如果 // 字符串的前后两段都找到了,输出该串即可~ // // 感悟: // // 这道题目的话,就是字典树上的暴力嘛,细节方面还是要多多注意 // val值还是不能少哟~因为查找到了该串,不一定是一个单词,可能 // 是中间的一个节点,即某个字符串的前缀~~~

hdu 1075:What Are You Talking About(字典树,经典题)

What Are You Talking About Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 102400/204800 K (Java/Others)Total Submission(s): 12617    Accepted Submission(s): 4031 Problem Description Ignatius is so lucky that he met a Martian yesterday. But

异或最大值(01字典树)

/** 异或最大值(01字典树) 题意:求n个非负数中任意2个的异或值的最大值.n数量级为10^5 分析:直接暴力肯定超时了.一个非负整数可以看成1个32位的01字符串,n个数可以看成n个字符串,因此可以建立字典树, 建好树后,对于任意非负整数x,可以沿着树根往下贪心找到y,使得x异或y最大,复杂度为树的深度. */ #include <stdio.h> #include <string.h> #include <algorithm> #include <iost

Tire树(字典树)

from:https://www.cnblogs.com/justinh/p/7716421.html Trie,又经常叫前缀树,字典树等等.它有很多变种,如后缀树,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree.当然很多名字的意义其实有交叉. 定义 在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串.与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定.一个节点的所有

hihoCoder 1014 Trie树(基础字典树)

题意  中文 最基础的字典树应用噢噢噢噢 #include<cstdio> #include<cstring> using namespace std; struct trie { trie *chi[26]; int num; trie() { num = 0; for(int i = 0; i < 26; ++i) chi[i] = NULL; } }*root; void insertTrie(char s[]) { trie *p = root; p->num+