最近刷题时连续遇到两道字典树的题目,所以做一下这个数据结构的总结。
首先什么叫做字典树?
叫
是
我 想
看
听
这种树结构并且把文字或者英文放在里面组成的叫做字典树。
那么字典树有什么用呢?
通过几道题目的练习我发现,字典树主要应用在,对于字符串的分级匹配和查询。
比如在我们如果有三句话,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