zoukankan      html  css  js  c++  java
  • Trie字典树(学习笔记)

    简介

    Trie字典树,又称单词查找树,是一种树形结构,是哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。  ---引自<<百度某科>>

    不管上面那一串东西(≧▽≦)/,直接开始.

    如图,这就是一棵Trie树,树的每条边上恰好对应一个字符,每个节点代表从根到该节点的路径所对应的字符串(也就是按顺序将所有经过的边上的字符连接起来),比如左下角这个叶节点代表的就是abcd.

    此外,节点上还能存储额外的信息.这就要看题目的要求灵活变通了.

    注意,Trie树的根节点为空,根节点表示空串.

    对于任意一个节点,它到它的子结点边上的字符都互不相同(这不是废话么)

    操作实现

    初始化:一颗空Trie仅包含一个根节点,且根节点的字符指针指向空.

    int tot=1;
    //tot=1,表示新建一个空节点作为Trie树的根
    

    插入操作:当需要插入一个字符串S时,我们令一个指针u先指向根节点.然后依次扫面字符串S中的每个字符c:

    (ch[u][c])表示节点u的c字符指针指向的节点,(bj[u]=1)表示从根到该节点u所经过的边上的字符所构成的字符串是题目给出的(我们输入的)一个完整的字符串,(tot)是Trie树中节点总数.

    (1) 若指针u的c字符指针指向一个已经存在的节点,则令指针u等于该节点(即(u=ch[u][c])).

    (2) 若指针u的c字符指针指向空,则新建一个节点(即(tot++)),然后令指针u的c字符指针指向该新节点(即(ch[u][c]=tot)),再令指针u等于该新节点(即(u=ch[u][c])).

    (3) 当字符串S中的字符全部扫描完毕时,在当前节点指针u(即字符串S最后一个字符所指向的位置)打个标记,标记该节点是某一个字符串的末尾(这是为了查询前缀和等操作方便).

    void insert(char *s){
        int u=1;
        int len=strlen(s);
        for(int i=0;i<len;i++){
    		int c=s[i]-'0';
    		if(!ch[u][c])ch[u][c]=++tot;
    		u=ch[u][c];
        }
        bj[u]=1;
        return;
    }
    

    主函数部分:

    char s[10];
    for(int i=1;i<=n;i++){
    	scanf("%s",s);
    	insert(s);
    }
    

    查询操作:当需要查询一个字符串S是否在Trie树中时,我们还是令一个指针u先指向根节点,然后依次扫描字符串S中的每个字符c:

    (1) 若指针u的c字符指针指向空,则说明Trie树中不存在字符串S,结束查询.(即(if(!ch[u][c])return false))

    (2) 若指针u的c字符指针指向一个已经存在的节点,则令u等于该节点.(即(u=ch[u][c]))

    (3) 当字符串S中所有字符扫描完毕时,若当前节点u被打了标记,即表示当前节点u是在一个字符串的末尾,说明字符串S在Trie树中已经存在,否则说明不存在.

    (即

    if(bj[u])return true;
    
    else return false;
    

    )

    完整查询代码:

    bool find(char *s){
    	int len=strlen(s);
        int u=1;
        for(int i=0;i<len;i++){
        	int c=s[i]-'a';
            if(!ch[u][c])return false;
            u=ch[u][c];
        }
        if(bj[u])return true;
        else return false;
    }
    

    来看几道模板题~\(≧▽≦)/~

    双倍经验 双倍快乐

    题意:给定n个数字串,判断其中是否存在一个数字串是另一个数字串的前缀.

    分析:Trie树就是为找前缀而生的吧.本题就作为模板题了.考虑把所有数字串构成一棵Trie树,在构建过程中就可以顺便判断答案了:

    (1)若当前串插入后没有新建任何节点,则当前串肯定是之前插入的某个串的前缀;

    (2)若插入过程中,有某个经过的节点带有串结尾的标记,则之前插入的某个串是当前串的前缀;

    int T,tot;char s[15];
    int ch[100005][15],bj[100005];
    bool insert(char *s){
        bool flag=false;
        int u=1,len=strlen(s);
        for(int i=0;i<len;i++){
    		int a=s[i]-'0';
    		if(!ch[u][a])ch[u][a]=++tot;
    		else if(i==len-1)flag=true;
    //情况1:没有插入任何新节点;
    		u=ch[u][a];
    		if(bj[u])flag=true;
    //情况2:经过某个有标记的节点;
        }
        bj[u]=1;
        return flag;
    }
    int main(){
        T=read();
        while(T--){
    		tot=1;//多组数据,记得把Trie树初始化
    		memset(ch,0,sizeof(ch));
    		memset(bj,0,sizeof(bj));
    		int n=read(),ans=0;
    		for(int i=1;i<=n;i++){
    	    	scanf("%s",s);
    	    	if(insert(s))ans=1;
    		}
    		if(!ans)puts("YES");
    		else puts("NO");
        }
        return 0;
    }
    
    

    传送门

    题意:有n个人,每个人的名字是长度不超过50的小写字符串.有m次点名,如果该名字正确且是第一次点到,输出“OK”,如果该名字错误,输出“WRONG”,如果该名字正确但不是第一次点到,输出“REPEAT”.

    分析:就是两个基本操作(插入和查询),唯一需要注意的是判重,在查询cheek的时候用flag数组做个标记,记录是否已经被点到过.

    int n,m,tot=1,ans;
    int ch[1000005[27],bj[1000005];
    int flag[1000005];
    void insert(string s){
        int u=1,len=s.size();
        for(int i=0;i<len;i++){
    		int a=s[i]-'a';
    		if(!ch[u][a])ch[u][a]=++tot;
    		u=ch[u][a];
        }
        bj[u]=1;
        return;
    }
    int cheek(string s){
        int u=1,len=s.size();
        for(int i=0;i<len;i++){
    		int a=s[i]-'a';
    		if(!ch[u][a])return 0;
    		u=ch[u][a];
        }
        if(!flag[u])flag[u]=1;
    //如果是第一次点到,就把该名字的flag数组标记为1
        else ans=1;
    //否则,就把ans记为1,表示重复点到.
        return 1;
    //return 1仅表示此次点名的名字是合法的.
    }
    int main(){
        n=read();
        for(int i=1;i<=n;i++){
    		string s;cin>>s;
    		insert(s);
        }
        m=read();
        while(m--){
    		ans=0;
    		string s;cin>>s;
    		if(cheek(s)){
    	    	if(ans==1)puts("REPEAT");
    	    	else puts("OK");
    		}
    		else puts("WRONG");
        }
        return 0;
    }
    
    
  • 相关阅读:
    AcWing 1135. 新年好 图论 枚举
    uva 10196 将军 模拟
    LeetCode 120. 三角形最小路径和 dp
    LeetCode 350. 两个数组的交集 II 哈希
    LeetCode 174. 地下城游戏 dp
    LeetCode 面试题 16.11.. 跳水板 模拟
    LeetCode 112. 路径总和 递归 树的遍历
    AcWing 1129. 热浪 spfa
    Thymeleaf Javascript 取值
    Thymeleaf Javascript 取值
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10329748.html
Copyright © 2011-2022 走看看