zoukankan      html  css  js  c++  java
  • 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
      - Suffix trie are a space-efficient data structure to store a string that allows many kinds of queries to be answered quickly.
      - Suffix 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
    ", 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("
    ");
    }
    
    // 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.

  • 相关阅读:
    Android 的快速增长让一些开发人员担忧 狼人:
    Android程序开发入门教程 狼人:
    2010:Android 商机之年 狼人:
    控制力的较量 Android上的博弈 狼人:
    深度解析Windows Phone 7 开发 狼人:
    图解iPhone开发入门教程 狼人:
    用Android LiveCD体验Android 操作系统的魅力 狼人:
    Android 教程之实现动作感应技术 狼人:
    Android版多功能日历,欢迎大家测试 狼人:
    iPhone、Windows Mobile、Symbian、Android移动开发前景分析 狼人:
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7351397.html
Copyright © 2011-2022 走看看