• 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;
    }
    
    
  • 相关阅读:
    js注意点:数组比较大小方法及数组与对象的区别
    ubuntu开通ftp虚拟用户
    linux(centos)禁止升级内核的办法
    解决ubuntu新建用户后,tab键不能使用的问题
    ubuntu下php7+mysql+nginx安装笔记
    使用nginx+lua+GraphicsMagick实现图片自动 裁剪
    mysql5.7配置文件(仅供参考)
    ubuntu下安装基于Apache的SVN服务器
    Linux CentOS 7.X 如何修改内核启动默认顺序
    Linxu下Redis安装
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10329748.html
Copyright © 2020-2023  润新知