Trie树沉思录(1)

发现自己已经很久没有写解题报告了,很大一部分是因为懒,做完题之后不想再怎样了~不过最近发现写解题报告确实是有好处的,一方面可以复习,一方面可以梳理。还有就是可以给自己的岁月留下一点什么东西~今天是五一劳动节,就应该要劳动!我要重新着手写我的博客了~

最近几个星期都在研究字符串,有点难,不过到现在为止Trie数学得还算是有那么点意思,写篇博文来记录一下!

(关于Trie数是什么东西我就不想写了,我只写我个人的一些思考)

Trie树通过共享前缀来达到了节约内存的目标,十分的强大!关于他的实现大概有两种:指针和二维数组!我自己学的是白书上面的二维数组。关于两种的区别,据航姐姐说,对于题目只有一组测试数据的,那种都可以,对于有多重测试数据的,二维数组比较好,以为在初始化比较的方便!接下来我具体地说一下二维数组的实现细节!

二维数组的实现个人觉得非常的强大!他充分利用了二维数据的各个部分进行数据存储!我们假设一个二维数组ch[i][j] = k,那么他的各部分的意思解释如下:

i :当前节点的父节点的编号

j:当前节点的字符对应的数字的值;

k:当前节点的编号

为了记录当前节点是否为单词的结尾,我们引入了一个新的数组val[],如果当前节点是单词的结尾,值为1,否则为0,这样我们就可以非常高效的判断某一个单词的结尾了~

对于插入和寻找的细节,我打算在代码中注释给大家~

接下来我们来看几道题:

HDOJ 1251:http://acm.hdu.edu.cn/showproblem.php?pid=1251

经典的入门题:

给你一堆单词,然后后面给一些前缀,问你以这些前缀为开头的单词有多少个?

#include <iostream>
#include <cstring>
#include <cstdio>
#define N 26
#define M 1000000

using namespace std;

int ch[M][N];//trie树
int val[M];//记录当前的节点是否为单词的结尾
int sub[M];//记录以当前节点为前缀的单词的次数
int allnode;//节点总数

void initial( )
{
    memset( val, -1, sizeof( val ) );
    memset( sub, 0, sizeof( sub ) );
    memset( ch[0], 0, sizeof( ch[0] ) );
    allnode = 1;//一开始只有根节点
}

int trans( char c )
{
    return c - ‘a‘;
}

void Insert( char *s, int v )
{
    int curnode = 0;//所有单词的一开始都是以根节点为父节点的
    int len = strlen( s );
    for( int i = 0; i < len; i++ )
    {
        int c = trans( s[i] );
        if( !ch[curnode][c] )//节点不存在,开辟新的节点
        {
            memset( ch[allnode], 0, sizeof( ch[allnode] ) );//开辟以当前节点为父节点的ch数组并初始化
            val[allnode] = 0;//开辟一个新的结尾数组
            ch[curnode][c] = allnode++;//新节点的编号就是当前的节点数
        }
        curnode = ch[curnode][c];//不管用不用新建节点,都要更新父节点
        sub[curnode]++;//放在上一句的后面是为了避免根节点
    }
    val[curnode] = v;//最后跳出循环的curnode和allnode一定相等,将一个非零的值付给最后一个节点的val数组
    //printf( "w" );
}

int Find( char *s )//寻找过程与插入十分地接近
{
    //printf( "w" );
    int len = strlen( s );
    int curnode = 0;//一开始还是以根节点开始
    //cout << len << endl;
    for( int i = 0; i < len; i++ )
    {
        int c = trans( s[i] );
        if( !ch[curnode][c] )
        {
            return 0;
        }
        else
        {
            curnode = ch[curnode][c];
        }
        //该循环是为了找到是否存在当前的前缀,如果存在,找出返回以该前缀的的最后一个字符的sub数组的值就是以该前缀为开始的单词数
    }
    //printf( "w" );
    return sub[curnode];
}

int main()
{
    //freopen( "in.txt", "r", stdin );
    char str[N] = {0};
    initial();
    int i = 1;
    while( gets( str ) && str[0] )
    {
        //cout << str << endl;
        Insert( str, i++ );
    }
    //Find( str );
    while( gets( str ) )
    {
        //printf( "wandm");
        //puts( str );
        printf( "%d\n", Find( str ) );
    }
}

HDOJ 1671 :http://acm.hdu.edu.cn/showproblem.php?pid=1671

给你一堆电话号码,如果里面没有任何一个是另外一个的前缀,输出yes,否则输出no。

思路:每读入一个电话号码,插入字典树,并进行寻找。这里的寻找有两种情况:长找短,短找长(即当前有可能是前缀要找是有以之为前缀的电话号码,或者是不是前缀,即要在树中找到是否有前缀)。如果是第一种情况,只要在发生失配的时候判断当前的节点是否为结尾即可;第二种情况,只要完全匹配,这说明存在以当前号码为前缀的的号码~

有点啰嗦,看代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 500000
#define M 10
#define CJ -1

using namespace std;

int ch[N][M];
int val[N];
int allnode;

void initial()
{
    memset( ch[0], 0, sizeof( ch[0] ) );
    memset( val, -1, sizeof( val ) );
    allnode = 1;
}

void Insert( char *s, int v )
{
    int curfather = 0;
    int len = strlen( s );
    for( int i = 0; i < len; i++ )
    {
        if( !ch[curfather][s[i]-‘0‘] )
        {
            memset( ch[allnode], 0, sizeof( ch[allnode] ) );
            val[allnode] = 0;
            ch[curfather][s[i]-‘0‘] = allnode++;
        }
        curfather = ch[curfather][s[i]-‘0‘];
    }
    val[curfather] = v;
}

int Find( char *s )//false means NO,
{
    int len = strlen( s );
    int sub = 0;
    int curfather = 0;
    for( int i = 0; i < len; i++ )
    {
        if( !ch[curfather][s[i]-‘0‘] )
        {
            if( val[curfather] > 0 ) //当前点为单词的最后一个节点
            {
                return false;
            }
            else
            {
                return CJ;
            }
        }
        else
        {
            curfather = ch[curfather][s[i]-‘0‘];
            sub++;
        }
    }
    if( sub == len )
    {
        return false;
    }
    else
    {
        return CJ;
    }
}

int main()
{
    char str[12] = {0};
    int t;
    scanf( "%d", &t );
    getchar();
    //start:
    while( t-- )
    {
        int num;
        //char ans[5] = {0};
        bool no = false;
        scanf( "%d", &num );
        //cout << num << endl;
        getchar();
        initial();
        for( int i = 1; i <= num; i++ )
        {
            scanf( "%s", str );
            //cout << str << endl;
            if( Find( str ) == false )
            {
                //cout << "NO" << endl;
                //strcmp( ans, "NO" );
                no = true;
                continue;
                //goto start;
            }
            else
            {
                if( !no )
                {
                    Insert( str, i );
                }
            }
        }
        if( no )
        {
            cout << "NO" << endl;
        }
        else
        {
            cout << "YES" << endl;
        }
        //cout << "YES" << endl;
    }
    return 0;
}

本来还做了另一道题的,不过一直WA,也不知道是为什么?争取今天把它A了~

那就先这样吧~

这三天对我很重要!!!



Trie树沉思录(1),布布扣,bubuko.com

时间: 2024-12-30 03:33:46

Trie树沉思录(1)的相关文章

C++沉思录第八章算数表达式树的面向对象问题的分析

刚开始看沉思录,觉得太枯燥.到了第八章,作者关于面向对象问题的分析,我follow书上的设计开发,理解了一些以前只是在书上看到的概念. 给自己做几点注解吧: 1.虚基类用来表达所有的继承类的共有特点,在这个例子中,所有的继承类都要有输出和求值计算,所以我们把这两个函数定义为虚函数. 2.虚基类必须至少含有一个纯虚函数.该纯虚函数可以定义也可以不定义. 3.我们要保证由虚基类派生出来的类的对象能被正确的析构,所以将虚基类的析构函数定义为虚函数. 4.对于虚函数如果没有定义,也应该使用一对{}来表明

poj3630 Phone List (trie树模板题)

Phone List Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 26328   Accepted: 7938 Description Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogu

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

Trie树学习2

数组实现的Trie树 字符容量有限,可以使用链表实现更为大容量的Trie #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <map> #include <set> #include <algorithm> #include <cstdlib> #

trie树(字典树)

1. trie树,又名字典树,顾名思义,它是可以用来作字符串查找的数据结构,它的查找效率比散列表还要高. trie树的建树: 比如有字符串"ab" ,"adb","adc"   可以建立字典树如图: 树的根节点head不存储信息,它有26个next指针,分别对应着字符a,b,c等.插入字符串ab时,next['a'-'a']即next[0]为空,这是申请一个结点放在next[0]的位置,插入字符串db时,next['d'-'a']即next[3]

C++沉思录之二——虚函数使用的时机

虚函数使用的时机 为什么虚函数不总是适用? 1. 虚函数有事会带来很大的消耗: 2. 虚函数不总是提供所需的行为: 3. 当我们不考虑继承当前类时,不必使用虚函数. 必须使用虚函数的情况: 1. 当你想删除一个表面上指向基类对象,实际却是指向派生类对象的指针,就需要虚析构函数. C++沉思录之二--虚函数使用的时机,布布扣,bubuko.com

Trie树

Trie树,即字典树或单词查找树,主要用于大量字符串的检索.去重.排序等操作. 主要原理就是利用字符串的公共前缀建立一棵多叉树,牺牲空间换取时间. 1 //Trie树 2 #include <iostream> 3 #include <string> 4 using std::cin; 5 using std::cout; 6 using std::endl; 7 using std::string; 8 9 const int SIZE = 26; 10 const char B

bzoj4103异或运算 可持久化trie树

要去清华冬令营了,没找到2016年的题,就先坐一坐15年的. 因为n很小,就按照b串建可持久化trie树,a串暴力枚举. 其他的直接看代码. #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; inline int read() { int x=0,f=1,ch=getchar(); while(ch<'0'||ch

hihoCoder 1014 Trie树

#1014 : Trie树 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一本词典,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能对于每一个我给出的字符串,都在这个词典里面找到以这个字符串开头的所有单词呢?” 身经百战的小Ho答道:“怎么会不能呢!你每给我一个字符串,我就依次遍历词典里的所有单词,检查你给我的字