Trie樹:應用于統計和排序

1. 什么是trie樹

1.Trie樹 (特例結構樹)  

      Trie樹,又稱單詞查找樹、字典樹,是一種樹形結構,是一種哈希樹的變種,是一種用于快速檢索的多叉樹結構。典型應用是用于統計和排序大量的字符串(但不僅限于字符串),所以經常被搜索引擎系統用于文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

     Trie的核心思想是空間換時間。利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。

     Trie樹也有它的缺點,Trie樹的內存消耗非常大.當然,或許用左兒子右兄弟的方法建樹的話,可能會好點.

2.  三個基本特性:  

1)根節點不包含字符,除根節點外每一個節點都只包含一個字符?! ?/span>

2)從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串?!?/span>

3)每個節點的所有子節點包含的字符都不相同。

3 .例子

       和二叉查找樹不同,在trie樹中,每個結點上并非存儲一個元素。
       trie樹把要查找的關鍵詞看作一個字符序列。并根據構成關鍵詞字符的先后順序構造用于檢索的樹結構。
       在trie樹上進行檢索類似于查閱英語詞典。

      一棵m度的trie樹或者為空,或者由m棵m度的trie樹構成。

     例如,電子英文詞典,為了方便用戶快速檢索英語單詞,可以建立一棵trie樹。例如詞典由下面的單詞成:a、b、c、aa、ab、ac、ba、ca、aba、abc、baa、bab、bac、cab、abba、baba、caba、abaca、caaba

1.png

           再舉一個例子。給出一組單詞,inn, int, at, age, adv, ant, 我們可以得到下面的Trie:

        2.gif

        可以看出:

  • 每條邊對應一個字母。

  • 每個節點對應一項前綴。葉節點對應最長前綴,即單詞本身。

  • 單詞inn與單詞int有共同的前綴“in”, 因此他們共享左邊的一條分支,root->i->in。同理,ate, age, adv, 和ant共享前綴"a",所以他們共享從根節點到節點"a"的邊。

查詢操縱非常簡單。比如要查找int,順著路徑i -> in -> int就找到了。

 2. trie樹的實現

1.插入過程

對于一個單詞,從根開始,沿著單詞的各個字母所對應的樹中的節點分支向下走,直到單詞遍歷完,將最后的節點標記為紅色,表示該單詞已插入trie樹。

2. 查找過程

其方法為:

(1) 從根結點開始一次搜索;

(2) 取得要查找關鍵詞的第一個字母,并根據該字母選擇對應的子樹并轉到該子樹繼續進行檢索;

(3) 在相應的子樹上,取得要查找關鍵詞的第二個字母,并進一步選擇對應的子樹進行檢索。

(4) 迭代過程……

(5) 在某個結點處,關鍵詞的所有字母已被取出,則讀取附在該結點上的信息,即完成查找。其他操作類似處理.

       即從根開始按照單詞的字母順序向下遍歷trie樹,一旦發現某個節點標記不存在或者單詞遍歷完成而最后的節點未標記為紅色,則表示該單詞不存在,若最后的節點標記為紅色,表示該單詞存在。如下圖中:trie樹中存在的就是abc、d、da、dda四個單詞。在實際的問題中可以將標記顏色的標志位改為數量count等其他符合題目要求的變量。  

      3.jpg

代碼:

// stdafx.h : include file for standard system include files,  
// or project specific include files that are used frequently, but  
// are changed infrequently  
//  
  
#pragma once  
  
#include <stdio.h>    
#include "stdlib.h"  
#include <iostream>  
#include <string.h>  
using namespace std;  
  
//宏定義      
#define TRUE   1      
#define FALSE   0     
#define NULL 0  
#define OK    1      
#define ERROR   0    
#define INFEASIBLE -1      
#define OVERFLOW -2    
  
const int num_chars = 26;  
class Trie {  
public:  
    Trie();  
    Trie(Trie& tr);  
    virtual ~Trie();  
    int trie_search(const char* word, char* entry ) const;  
    int insert(const char* word, const char* entry);  
    int remove(const char* word, char* entry);  
protected:  
     struct Trie_node{  
           char* data; //若不為空,表示從root到此結點構成一個單詞   
           Trie_node* branch[num_chars]; //分支   
           Trie_node(); //構造函數   
     };  
       
     Trie_node* root; //根結點(指針)   
  
};
// Test.cpp : Defines the entry point for the console application.    
//    
#include "stdafx.h"   
Trie::Trie_node::Trie_node() {  
    data = NULL;      
    for (int i=0; i<num_chars; ++i)   
        branch[i] = NULL;  
}  
Trie::Trie():root(NULL) {}  
Trie::~Trie(){}  
int Trie::trie_search(const char* word, char* entry ) const {    
    int position = 0;  //層數   
    char char_code;      
  
    Trie_node *location = root;  //從根結點開始   
    while( location!=NULL && *word!=0 ) {       
        if (*word >= 'A' && *word <= 'Z')   
            char_code = *word-'A';       
        else if (*word>='a' && *word<='z')   
            char_code = *word-'a';       
        else return 0;// 不合法的單詞     
        //轉入相應分支指針   
        location = location->branch[char_code];       
        position++;       
        word++;    
    }    
    //找到,獲取數據,成功返回   
    if ( location != NULL && location->data != NULL ) {       
        strcpy(entry,location->data);       
        return 1;    
    }    
    else  return 0;// 不合法的單詞  
}  
int Trie::insert(const char* word, const char* entry) {     
    int result = 1, position = 0;     
    if ( root == NULL ) root = new Trie_node;   //初始插入,根結點為空   
    char char_code;     
    Trie_node *location = root;   //從根結點開始   
    while( location!=NULL && *word!=0 ) {         
        if (*word>='A' && *word<='Z') char_code = *word-'A';         
        else if (*word>='a' && *word<='z') char_code = *word-'a';         
        else return 0;// 不合法的單詞      
  
        //不存在此分支   
        if( location->branch[char_code] == NULL )              
            location->branch[char_code] = new Trie_node;    //創建空分支     
  
        //轉入分支   
        location = location->branch[char_code];         
        position++;word++;   }     
    if (location->data != NULL) result = 0;//欲插入的單詞已經存在      
    else {    //插入數據       
        location->data = new char[strlen(entry)+1];     //分配內存      
        strcpy(location->data, entry);    //給data賦值表明單詞存在   
    }     
    return result;    
}  
int main(){     
    Trie t;     
    char entry[100];     
    t.insert("a", "DET");          
    t.insert("abacus","NOUN");     
    t.insert("abalone","NOUN");     
    t.insert("abandon","VERB");     
    t.insert("abandoned","ADJ");    
    t.insert("abashed","ADJ");     
    t.insert("abate","VERB");      
    t.insert("this", "PRON");     
    if (t.trie_search("this", entry))        
        cout<<"'this' was found. pos: "<<entry<<endl;     
    if (t.trie_search("abate", entry))        
        cout<<"'abate' is found. pos: "<<entry<<endl;     
    if (t.trie_search("baby", entry))        
        cout<<"'baby' is found. pos: "<<entry<<endl;     
    else        
        cout<<"'baby' does not exist at all!"<<endl;  
}

3. 查找分析

       在trie樹中查找一個關鍵字的時間和樹中包含的結點數無關,而取決于組成關鍵字的字符數。而二叉查找樹的查找時間和樹中的結點數有關O(log2n)。

       如果要查找的關鍵字可以分解成字符序列且不是很長,利用trie樹查找速度優于二叉查找樹。如:
       若關鍵字長度最大是5,則利用trie樹,利用5次比較可以從26^5=11881376個可能的關鍵字中檢索出指定的關鍵字。而利用二叉查找樹至少要進行Trie樹:應用于統計和排序次比較。

3. trie樹的應用:

1. 字符串檢索,詞頻統計,搜索引擎的熱門查詢

        事先將已知的一些字符串(字典)的有關信息保存到trie樹里,查找另外一些未知字符串是否出現過或者出現頻率。

        舉例:

       1)有一個1G大小的一個文件,里面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。

       2)給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最早出現的順序寫出所有不在熟詞表中的生詞。

       3)給出一個詞典,其中的單詞為不良單詞。單詞均為小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那么文本problem含有不良單詞。

       4)1000萬字符串,其中有些是重復的,需要把重復的全部去掉,保留沒有重復的字符串

       5)尋找熱門查詢:搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節。假設目前有一千萬個記錄,這些查詢串的重復讀比較高,雖然總數是1千萬,但是如果去除重復和,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。

2. 字符串最長公共前綴

       Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當我們把大量字符串存儲到一棵trie樹上時,我們可以快速得到某些字符串的公共前綴。舉例:

      1) 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少.  解決方案:

        首先對所有的串建立其對應的字母樹。此時發現,對于兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,于是,問題就轉化為了離線  (Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。

       而最近公共祖先問題同樣是一個經典問題,可以用下面幾種方法:

        1. 利用并查集(Disjoint Set),可以采用采用經典的Tarjan 算法;

        2. 求出字母樹的歐拉序列(Euler Sequence )后,就可以轉為經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;

3.  排序

       Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串便是按字典序排序的結果。

        舉例: 給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。

4 作為其他數據結構和算法的輔助結構

       如后綴樹,AC自動機等。

轉自:http://blog.csdn.net/hguisu/article/details/8131559

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2714

(0)
s19930811s19930811
上一篇 2015-04-08 16:07
下一篇 2015-04-08 16:08

相關推薦

  • HAProxy服務配置

    HAProxy 是一款提供高可用性、負載均衡以及基于TCP(第四層)和HTTP(第七層)應用的代理軟件。 相較與 Nginx,HAProxy 更專注與反向代理,因此它可以支持更多的選項,更精細的控制,更多的健康狀態檢測機制和負載均衡算法。 四層和七層負載均衡的區別: 四層: 通過分析IP層及TCP/UDP層的流量實現的基于“IP+端口”的負載均衡。 七層: …

    Linux干貨 2017-05-19
  • 計算機的組成和Linux發展史

    計算機的組成及功能   計算機是由CPU,內存,輸入裝置和輸出裝置四大部件組成計算機,每一部件分別按要求執行特定的基本功能。  CPU: 控制器和運算器合稱中央處理器,也就是CPU,它的功能主要是解釋計算機指令以及處理計算機軟件中的數據。  內存: 它是與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內存中進行的,內存(Me…

    Linux干貨 2016-10-30
  • 第二周練習與作業

    第二周作業 1、Linux上的文件管理類命令有哪些,其常用的使用方法及其相關示例演示          文件管理類命令:cp,mv,rm cp: 源文件;目標文件          [root@loc…

    Linux干貨 2017-08-09
  • 軟鏈接,硬鏈接區別

    軟硬鏈接涉及文件系統inode, 區分于inode號,硬鏈接inode號與鏈接文件相同,且創建鏈接不占空間.而軟鏈接占名稱字節個空間,且inode號與鏈接文件不同; 兩者查找inode號命令都可查找inode號,命令為ls -i,如需查找本目錄要加d; 在創建鏈接環境上,硬鏈接只能在同分區創建一個,不能跨分區創建;而軟鏈接可以跨分區創建多個鏈接文件且可以多個…

    Linux干貨 2016-10-20
  • bash編程初體驗(三)

    bash編程初體驗之for for while until 概述 本文將介紹以for為代表的循環語句在shell 腳本中的應用,常見的循環語句有for, while,until,作為循環語句,顧名思義,它就是重復地做一件事,直到滿足某一條件而退出;另外,還有兩個循環控制語句continue與break來配合循環語句,以實現臨時中斷或跳出循環的功能;以下為fo…

    Linux干貨 2016-08-24
  • Linux 第六天: (08月03日) Linux權限管理

    Linux 第六天: (08月03日) Linux權限管理         chown USER:GROUP FILE 變更文件或目錄所屬主chown -R 遞歸chown –reference=<> 參考 chgrp GROUP DIR(or FILE) 變更文件或目錄所屬組   &…

    Linux干貨 2016-08-08
欧美性久久久久