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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字典树的基础,以及在实际项目中对于敏感词的替换的应用相关的知识,希望对你有一定的参考价值。

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

 

首先什么叫做字典树?

      叫     

      是  

我   想  

      看  

      听 

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

 

那么字典树有什么用呢?

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

 

比如在我们如果有三句话,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;//存在脏字返回脏字长度
        }
    }
}

以上是关于字典树的基础,以及在实际项目中对于敏感词的替换的应用的主要内容,如果未能解决你的问题,请参考以下文章

多模匹配算法之Aho-Corasick

Jsp敏感词过滤

Trie树的java实现

简述字典树

maven 主要介绍以及 javaee 中构建一词的概念

C#中的正则表达式