Trie树(字典树)(1)

  Trie树。又称字典树,单词查找树或者前缀树,是一种用于高速检索的多叉树结构。

  Trie树与二叉搜索树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。

一个节点的全部子孙都有同样的前缀(prefix),也就是这个节点相应的字符串,而根节点相应空字符串。普通情况下。不是全部的节点都有相应的值,仅仅有叶子节点和部分内部节点所相应的键才有相关的值。

  A trie, pronounced “try”, is a tree that exploits some structure in the keys

  - e.g. if the keys are strings, a binary search tree would compare the entire strings, but a trie would look at their individual characters

  - Suf?x trie are a space-ef?cient data structure to store a string that allows many kinds of queries to be answered quickly.

  - Suf?x trees are hugely important for searching large sequences.

  Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计。排序和保存大量的字符串(但不仅限于字符串)。所以常常被搜索引擎系统用于文本词频统计。

  一个典型的应用,就是在搜索时出现的搜索提示,比方我输入“花千”,就会出现“花千骨电视剧”,“花千骨小说”等提示。

  Let word be a single string and let dictionary be a large set of words. If we have a dictionary, and we need to know if a single word is inside of the dictionary the tries are a data structure that can help us. But you may be asking yourself, “Why use tries if set and hash tables can do the same?” There are two main reasons:

  1)The tries can insert and find strings in O(L) time (where L represent the length of a single word). This is much faster than set , but is it a bit faster than a hash table.

  2)The set and the hash tables can only find in a dictionary words that match exactly with the single word that we are finding; the trie allow us to find words that have a single character different, a prefix in common, a character missing, etc.

  Trie树的基本性质能够归纳为:

  1)根节点不包括字符,除根节点外的每一个节点仅仅包括一个字符。

  2)从根节点到某一个节点。路径上经过的字符连接起来,为该节点相应的字符串。

  3)每一个节点的全部子节点包括的字符串不同样。

Trie树的基本实现

  字典树的插入(Insert)、删除( Delete)和查找(Find)都很easy。用一个一重循环就可以,即第i 次循环找到前i 个字母所相应的子树,然后进行相应的操作。实现这棵字母树,我们用最常见的数组保存(静态开辟内存)就可以。当然也能够开动态的指针类型(动态开辟内存)。至于结点对儿子的指向,一般有三种方法:

  1)对每一个结点开一个字母集大小的数组,相应的下标是儿子所表示的字母,内容则是这个儿子相应在大数组上的位置,即标号。

  2)对每一个结点挂一个链表。按一定顺序记录每一个儿子是谁。

  3)使用左儿子右兄弟表示法记录这棵树。

  三种方法,各有特点。

第一种易实现。但实际的空间要求较大;另外一种。较易实现。空间要求相对较小,但比較费时;第三种,空间要求最小,但相对费时且不易写。

  这里採用第一种:

#include <stdio.h>
#include <iostream>
using namespace std;
#define  MAX    26

typedef struct TrieNode
{
    bool isEnd;
    int nCount;  // 该节点前缀出现的次数
    struct TrieNode *next[MAX]; //该节点的兴许节点
} TrieNode;

TrieNode Memory[1000000]; //先分配好内存。 malloc 较为费时
int allocp = 0;

//初始化一个节点。nCount计数为1。 next都为null
TrieNode * createTrieNode()
{
    TrieNode * tmp = &Memory[allocp++];
    tmp->isEnd = false;
    tmp->nCount = 1;
    for (int i = 0; i < MAX; i++)
        tmp->next[i] = NULL;
    return tmp;
}

void insertTrie(TrieNode * root, char * str)
{
    TrieNode * tmp = root;
    int i = 0, k;
    //一个一个的插入字符
    while (str[i])
    {
        k = str[i] - ‘a‘; //当前字符 应该插入的位置
        if (tmp->next[k])
        {
            tmp->next[k]->nCount++;
        }
        else
        {
            tmp->next[k] = createTrieNode();
        }

        tmp = tmp->next[k];
        i++; //移到下一个字符
    }
    tmp->isEnd = true;
}

int searchTrie(TrieNode * root, char * str)
{
    if (root == NULL)
        return 0;
    TrieNode * tmp = root;
    int i = 0, k;
    while (str[i])
    {
        k = str[i] - ‘a‘;
        if (tmp->next[k])
        {
            tmp = tmp->next[k];
        }
        else
            return 0;
        i++;
    }
    return tmp->nCount; //返回最后的那个字符  所在节点的 nCount
}

/*  During delete operation we delete the key in bottom up manner using recursion. The following are possible conditions when deleting key from trie:
Key may not be there in trie. Delete operation should not modify trie.
Key present as unique key (no part of key contains another key (prefix), nor the key itself is prefix of another key in trie). Delete all the nodes.
Key is prefix key of another long key in trie. Unmark the leaf node.
Key present in trie, having atleast one other key as prefix key. Delete nodes from end of key until first leaf node of longest prefix key.  */
bool deleteTrie(TrieNode * root, char * str)
{
    TrieNode * tmp = root;
    k = str[0] - ‘a‘;
    if(tmp->next[k] == NULL)
        return false;
    if(str == ‘\0’)
        return false;
    if(tmp->next[k]->isEnd && str[1] == ‘\0’)
    {
        tmp->next[k]->isEnd = false;
        tmp->next[k]->nCount--;
        if(tmp->next[k]->nCount == 0)  //really delete
        {
            tmp->next[k] = NULL;
            return true;
        }
        return false;
    }
    if(deleteTrie(tmp->next[k],  str+1)) //recursive
    {
        tmp->next[k]->nCount--;
        if(tmp->next[k]->nCount == 0)  //really delete
        {
            tmp->next[k] = NULL;
            return true;
        }
        return false;
    }
}

int main(void)
{
    char s[11];
    TrieNode *Root = createTrieNode();
    while (gets(s) && s[0] != ‘0‘) //读入0 结束
    {
        insertTrie(&Root, s);
    }

    while (gets(s)) //查询输入的字符串
    {
        printf("%d\n", searchTrie(Root, s));
    }

    return 0;
}

应用例一:

  Longest prefix matching – A Trie based solution

Given a dictionary of words and an input string, find the longest prefix of the string which is also a word in dictionary.

Examples:

  Let the dictionary contains the following words:

{are, area, base, cat, cater, children, basement}

Below are some input/output examples:

Input String     Output

caterer        cater

basemexy       base

child        < Empty >

Solution:

  We build a Trie of all dictionary words. Once the Trie is built, traverse through it using characters of input string. If prefix matches a dictionary word, store current length and look for a longer match. Finally, return the longest match.

// The main method that finds out the longest string ‘input‘
public String getMatchingPrefix(String input)  {
    String result = ""; // Initialize resultant string
    int length = input.length();  // Find length of the input string       

    // Initialize reference to traverse through Trie
    TrieNode crawl = root;   

    // Iterate through all characters of input string ‘str‘ and traverse
    // down the Trie
    int level, prevMatch = 0;
    for( level = 0 ; level < length; level++ )
    {
        // Find current character of str
        char ch = input.charAt(level);    

        // HashMap of current Trie node to traverse down
        HashMap<Character,TrieNode> child = crawl.getChildren();                        

        // See if there is a Trie edge for the current character
        if( child.containsKey(ch) )
        {
           result += ch;          //Update result
           crawl = child.get(ch); //Update crawl to move down in Trie

           // If this is end of a word, then update prevMatch
           if( crawl.isEnd() )
                prevMatch = level + 1;
        }
        else  break;
    }

    // If the last processed character did not match end of a word,
    // return the previously matching prefix
    if( !crawl.isEnd() )
            return result.substring(0, prevMatch);        

    else return result;
}

应用例二:

  Print unique rows in a given boolean matrix

Given a binary matrix, print all unique rows of the given matrix.

Input:

{0, 1, 0, 0, 1}

{1, 0, 1, 1, 0}

{0, 1, 0, 0, 1}

{1, 1, 1, 0, 0}

Output:

0 1 0 0 1

1 0 1 1 0

1 1 1 0 0

Method 1 (Simple)

  A simple approach is to check each row with all processed rows. Print the first row. Now, starting from the second row, for each row, compare the row with already processed rows. If the row matches with any of the processed rows, don’t print it. If the current row doesn’t match with any row, print it.

  Time complexity: O( ROW^2 x COL )

  Auxiliary Space: O( 1 )

Method 2 (Use Binary Search Tree)

  Find the decimal equivalent of each row and insert it into BST. Each node of the BST will contain two fields, one field for the decimal value, other for row number. Do not insert a node if it is duplicated. Finally, traverse the BST and print the corresponding rows.

  Time complexity: O( ROW x COL + ROW x log( ROW ) )

  Auxiliary Space: O( ROW )

  This method will lead to Integer Overflow if number of columns is large.

Method 3 (Use Trie data structure)

  Since the matrix is boolean, a variant of Trie data structure can be used where each node will be having two children one for 0 and other for 1. Insert each row in the Trie. If the row is already there, don’t print the row. If row is not there in Trie, insert it in Trie and print it.

  Below is C implementation of method 3.

//Given a binary matrix of M X N of integers, you need to return only unique rows of binary array
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define ROW 4
#define COL 5

// A Trie node
typedef struct Node
{
    bool isEndOfCol;
    struct Node *child[2]; // Only two children needed for 0 and 1
} Node;

// A utility function to allocate memory for a new Trie node
Node* newNode()
{
    Node* temp = (Node *)malloc( sizeof( Node ) );
    temp->isEndOfCol = 0;
    temp->child[0] = temp->child[1] = NULL;
    return temp;
}

// Inserts a new matrix row to Trie.  If row is already
// present, then returns 0, otherwise insets the row and
// return 1
bool insert( Node** root, int (*M)[COL], int row, int col )
{
    // base case
    if ( *root == NULL )
        *root = newNode();

    // Recur if there are more entries in this row
    if ( col < COL )
        return insert ( &( (*root)->child[ M[row][col] ] ), M, row, col+1 );

    else // If all entries of this row are processed
    {
        // unique row found, return 1
        if ( !( (*root)->isEndOfCol ) )
            return (*root)->isEndOfCol = 1;

        // duplicate row found, return 0
        return 0;
    }
}

// A utility function to print a row
void printRow( int (*M)[COL], int row )
{
    int i;
    for( i = 0; i < COL; ++i )
        printf( "%d ", M[row][i] );
    printf("\n");
}

// The main function that prints all unique rows in a
// given matrix.
void findUniqueRows( int (*M)[COL] )
{
    Node* root = NULL; // create an empty Trie
    int i;

    // Iterate through all rows
    for ( i = 0; i < ROW; ++i )
        // insert row to TRIE
        if ( insert(&root, M, i, 0) )
            // unique row found, print it
            printRow( M, i );
}

// Driver program to test above functions
int main()
{
    int M[ROW][COL] = {{0, 1, 0, 0, 1},
        {1, 0, 1, 1, 0},
        {0, 1, 0, 0, 1},
        {1, 0, 1, 0, 0}
    };

    findUniqueRows( M );

    return 0;
}

  Time complexity: O( ROW x COL )

  Auxiliary Space: O( ROW x COL )

  This method has better time complexity. Also, relative order of rows is maintained while printing.

时间: 2024-10-19 17:41:07

Trie树(字典树)(1)的相关文章

剑指Offer——Trie树(字典树)

剑指Offer--Trie树(字典树) Trie树 Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种.典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高. Trie的核心思想是空间换时间.利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的. Trie树也有它的缺点,Trie树的内存消耗非常大.当然,或许用左儿子右兄弟的方法建树的话,可能会好点.可见,优

Hash树(散列树)和Trie树(字典树、前缀树)

1.Hash树 理想的情况是希望不经过任何比较,一次存取便能得到所查的记录, 那就必须在记的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和一个唯一的存储位置相对应.因而在查找时,只要根据这个对应关系f找到 给定值K的像f(K).由此,不需要进行比较便可直接取得所查记录.在此,我们称这个对应关系为哈希(Hash)函数,按这个思想建立的表为哈希表. 在哈希表中对于不同的关键字可能得到同一哈希地址,这种现象称做冲突.在一般情况下,冲突只能尽可能地减少,而不能完全避免.因为哈希函数是从

【数据结构】前缀树/字典树/Trie

[前缀树] 用来保存一个映射(通常情况下 key 为字符串  value 为字符串所代表的信息) 例如:一个单词集合 words = {  apple, cat,  water  }   其中 key 为单词      value 代表该单词是否存在 words[ 'apple' ] = 存在     而     word[ ' abc' ] = 不存在 图示:一个保存了8个键的trie结构,"A", "to", "tea", "ted

【学习总结】数据结构-Trie/前缀树/字典树-及其最常见的操作

Trie/前缀树/字典树 Trie (发音为 "try") 或前缀树是一种树数据结构,用于检索字符串数据集中的键. 一种树形结构,是一种哈希树的变种. 典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计. 优点:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高. 应用: 自动补全 END 原文地址:https://www.cnblogs.com/anliux/p/12590368.html

9-11-Trie树/字典树/前缀树-查找-第9章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第9章  查找 - Trie树/字典树/前缀树(键树) ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Status.h.Scanf.c        相关测试数据

Trie树/字典树

Trie树结构 Trie树是一种树形数据结构,又称为单词查找树.字典树,是一种用于快速检索的多叉树结构.典型应用是统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计.     它的主要设计思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销.它的优点是可以最大限度的减少无谓的字符串比较,查询效率比哈希表高:缺点是内存消耗非常大. Trie树基本特性 根节点不包含字符,除根节点外每一个节点都只包含一个字符 从根节点到某一节点,路径上经过的字符连接起来,为该节点

常用算法之Trie【字典树,前缀树】

Trie中文名又叫做字典树,前缀树等,因为其结构独有的特点,经常被用来统计,排序,和保存大量的字符串,经常见于搜索提示,输入法文字关联等,当输入一个值,可以自动搜索出可能的选择.当没有完全匹配的结果时,可以返回前缀最为相似的可能. 其实腾讯的面试题有一个:如何匹配出拼写单词的正确拼写.其实用匹配树非常合适. 基本性质: 1.根节点不含有字符,其余各节点有且只有一个字符. 2.根节点到某一节点中经过的节点存储的值连接起来就是对应的字符串. 3.每一个节点所有的子节点的值都不应该相同. 借用一下维基

Trie树&mdash;字典树(单词查找树)

Trie树,又称字典树,单词查找树.它来源于retrieval(检索)中取中间四个字符构成的.用于存储大量的字符串以便支持快速模式匹配.主要应用在信息检索领域. Trie有三种结构:标准Trie(standard trie),压缩Trie,后缀Trie(suffix trie). 1.标准Trie 标准Trie树的结构:所有含有公共前缀的字符串将挂在树中同一个结点下.实际上trie简明的存储于串集合汇总的所有公共前缀.加入有这样一个字符串集合X{bear,bell,bid,bull,buy,se

Trie树/字典树题目(2017今日头条笔试题:异或)

1 /* 2 本程序说明: 3 4 [编程题] 异或 5 时间限制:1秒 6 空间限制:32768K 7 给定整数m以及n各数字A1,A2,..An,将数列A中所有元素两两异或,共能得到n(n-1)/2个结果,请求出这些结果中大于m的有多少个. 8 输入描述: 9 第一行包含两个整数n,m. 10 11 第二行给出n个整数A1,A2,...,An. 12 13 数据范围 14 15 对于30%的数据,1 <= n, m <= 1000 16 17 对于100%的数据,1 <= n, m,

[转载]Trie树|字典树(字符串排序)

有时,我们会碰到对字符串的排序,若采用一些经典的排序算法,则时间复杂度一般为O(n*lgn),但若采用Trie树,则时间复杂度仅为O(n). Trie树又名字典树,从字面意思即可理解,这种树的结构像英文字典一样,相邻的单词一般前缀相同,之所以时间复杂度低,是因为其采用了以空间换取时间的策略. 下图为一个针对字符串排序的Trie树(我们假设在这里字符串都是小写字母),每个结点有26个分支,每个分支代表一个字母,结点存放的是从root节点到达此结点的路经上的字符组成的字符串. 将每个字符串插入到tr