zoukankan      html  css  js  c++  java
  • 字典树【入门介绍】

    什么是字典树?

    字典树(又叫单词查找树、TrieTree),是一种树形结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串,如01字典树)。主要思想是利用字符串的公共前缀来节约存储空间。很好地利用了串的公共前缀,节约了存储空间。字典树主要包含两种操作,插入和查找。
    在这里插入图片描述

    字典树的优点

    • 节约空间

    例如在现实生活中,我们需要存储很多URL(Uniform Resoure Locator:统一资源定位器)是WWW页的地址。
    大多数以“http://www ”开头,若以字典树存储,则50亿个“http://www ”只需要10个存储单位即可,而非50亿*10个。

    • 快速检索
      字典树能很好地利用串的公共前缀,节约了存储空间,同时用它来检索同样有着比较高的效率。
      我们需要将一个单词列表建出一棵单词查找树,满足:
      (1)根结点不包含字母,除根结点外的每个都仅含一个大写英文字母;
      (2)从根结点到某一节点,路径上经过的字母依次连起来所构成的单词,称为该结点对应的单词。单词列表中的每个词,都是该单词查找树某个结点所对应的单词;
      (3)在满足上述条件下,该单词查找树的结点数最少,统计出该单词查找树的结点数目。
      在这里插入图片描述

    字典树的结构

    在这里插入图片描述
    字典树可以解决以下问题:
    在题目给出的输入字典中查询单词是否存在,以及以某单词为前缀查询字典中有多少符合条件的单词

    例题:
    题意:给出一些单词找出其中某些可以由另外两个单词拼接而成的单词

    样例输入             样例输出
    a                   ahat
    ahat                hatword
    hat 
    hatword 
    hziee 
    word
    

    分析:如果按最朴素的方法实现,即对于输入的每个单词依次进行遍历,如第二个单词‘ahat’,要查询其是否由输入的其他单词组合而成,就要将ahat拆开,变为a+hat,ah+at,aha+t,对输入的n个单词再查找一轮是否有一样匹配的字符。也就是n(个单词)*len-1(每个单词长度)*n(个单词) 的查询次数两两单词进行比对时用到strcmp函数又要花费len的查询次数。

    若在查询过程中使用字典树进行查找。依然对每个单词进行遍历。然后将n个单词分别拆开,两段分开的单词进行字典树查询。

    如果两段单词都在字典树中查询成功说明被查询单词即其中一个ans。

    对字典树进行查询时,无须遍历n个单词,只用对被查询单词在树上走出len的查询次数。


    字典树还有很多除了字符串之外的应用,如对数字的二进制01串构造字典树可以快速按位查询数字是否存在。当结合字符串匹配算法KMP时,利用KMP的利用失配信息思想快速继续匹配,可以实现AC自动机算法。只需给每个节点增加fail指针指向失配时快速指向的节点。

    当对构造好的字典树进行BFS预处理并增加直达叶子节点的指针时,可以解决输入最小操作数的问题。

    总之以字典树为基础可以改造出各种有用而高效的处理方法。

    数组模拟字典树:

    #include <stdio.h>
    #include <string.h>
    const int maxn=10000;//提前估计好可能会开的节点的个数
    
    int tot;            //节点编号,模拟申请新节点,静态申请
    int trie[10000][26]; //假设每个节点的分支有26个
    bool isw[10000];     //判断该节点是不是单词结尾
    
    void insert(char *s,int rt)//投入的参数是一个字符串和节点数,建立字典树
    {
        for(int i=0; s[i]; i++)
        {
            int x=s[i]-'a';//假设单词都是小写字母组成
            if(trie[rt][x]==0) //没有,申请新节点
            {
                trie[rt][x]=++tot;//每个字符的编号
            }
            rt=trie[rt][x];
        }
        isw[rt]=true;//整个字符串读完后,在isw数组中记录第rt层为单词结尾
    }
    
    bool find(char *s,int rt)
    {
        for(int i=0; s[i]; i++)
        {
            int x=s[i]-'a';//假设单词都是小写字母组成
            if(trie[rt][x]==0)
            {
                return false;
            }
            rt=trie[rt][x];
        }
        return isw[rt];
    }
    
    char s[22];//单词读入
    
    int main()
    {
        tot=0;//一开始没有节点
    
        int rt=++tot;//申请一个根节点
        memset(trie[rt],0,sizeof(trie[rt]));//初始化根节点
        memset(isw,false,sizeof(isw));
    
        while(scanf("%s",s),s[0]!='#') //新建字典,以一个'#'结束
        {
            insert(s,rt);
        }
        while(scanf("%s",s),s[0]!='#') //查单词,以一个'#'结束
        {
            if(find(s,rt))//从字典中查找单词
                printf("%s 在字典中
    ",s);
            else
                printf("%s 不在字典中
    ",s);
        }
        return 0;
    }
    
    

    指针型字典树:

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    struct trie//trie型结构体,建立树
    {
        int cnt;//用作标记查找
        trie *next[26];//连接树与节点的指针
        bool flag;///单词结尾节点标记
    };
    trie *root=new trie;//建立根节点
    
    void insert(char ch[])
    {
        trie *p=root,*newnode;
        for(int i=0; ch[i]!=''; i++)
        {
            if(p->next[ch[i]-'a']==0)//这里以所有的字符全部为小写字母为例
            {
                newnode=new trie;///创建新节点
                newnode.flag=false;///初始化新节点结尾标记
                for(int j=0; j!=26; j++)
                {
                    newnode->next[j]=NULL;///初始化所有孩子
                }
                newnode->cnt=1;///新节点次数初始化
                p->next[ch[i]-'a']=newnode;
                p=newnode;
            }
            else
            {
                p=p->next[ch[i]-'a'];
                p->cnt++;///对走过的节点次数进行计数
            }
        }
        p->flag=true;///标记结尾
    }
    
    int find(char ch[])///查询
    {
        trie *p=root;
        for(int i=0; ch[i]!=''; i++)
        {
            if(p->next[ch[i]-'a']!=NULL)
                p=p->next[ch[i]-'a'];
            else
                return 0;
        }
        return p->cnt;
    }
    
    int deal(trie* T)//递归将字典树清空
    {
        int i;
        if(T==NULL)
            return 0;
        for(i=0;i<10;i++)
        {
            if(T->next[i]!=NULL)
                deal(T->next[i]);
        }
        free(T);
        return 0;
    }
    
    int main()
    {
        char ch[20];
        for(int i=0; i!=26; i++)
        {
            root->next[i]=NULL;
        }
        root->cnt=0;
        while(gets(ch))
        {
            if(!strcmp(ch,""))
                break;
            insert(ch);
        }
        while(scanf("%s",ch)!=EOF)
        {
            printf("%d
    ",find(ch));
        }
        deal(root);
        return 0;
    }
    
    
  • 相关阅读:
    linux命令df中df -h和df -i的区别
    linux系统df和du命令的区别
    Linux type命令的用法
    《DNS的正向反向解析》RHEL6
    《DNS服务缓存的建立》RHEL6
    《服务器的追踪与审计》RHEL6
    《LDAP服务器和客户端的加密认证》RHEL6——第二篇 运维工程师必考
    《ISCSI集中存储》RHEL6——CE
    《LDAP服务器的配置与客户端的测试》RHEL6——第一篇 运维工程师必考
    《RHEL6硬盘的分区和swap分区管理》——硬盘分区的大总结
  • 原文地址:https://www.cnblogs.com/kuronekonano/p/11794294.html
Copyright © 2011-2022 走看看