字典树

转自:http://book.51cto.com/art/201008/220537.htm

8.38  什么是字典树

字典树(Trie)和后缀树是单词处理的最流行数据结构。字典树于1960年由Fredkin作为搜索和排序数字数据的有效方法引入。名称Trie来自Information Retrieval(信息检索),这是一种特殊类型的树,它存储字符串并使其能够快速检索。如果我们仔细观察的话,就会注意到, 字典树不是别的什么东西,而是有向无环单词图(Directed Acyclic Word Graph,DAWG)。我们将在第9章中讲解这个术语。当我们需要频繁地从一组字符串中搜索一个字符串时,字典树是最佳数据结构的原因是,搜索时间为O(m),这里m是被搜索字符串的长度。搜索时间与集合中字符串的个数以及集合中最长字符串的长度无关。

在图8.33中,存储了单词BALK、 BALMY、BANAL、BANK和BANE。值得注意的是,这些字符串的一部分是相同的。这种树对于信息检索十分有用,并在下述领域得到应用。存在大量字典树的应用:

字典树_第1张图片 

字典表示(尽管二叉树是更佳的选择)。

近似字符串匹配。

拼法检查软件。

动态键匹配(不同联机系统上的个人身份认证)。

动态散列。

冲突解析算法。

领导人选举算法。

IP 地址查找。

编码。

多项式因式分解。

Lempel-Ziv压缩方案,等等。


8.39  如何使用链表建立字典树的模型

字典树能够轻易地使用链表表示。这里,我们将表示一个字典树,它存储从特定字母开始的单词。这棵字典树的每一个结点都使用下述结构表示。Next是一个打算保存结点的26个可能孩子的地址。

  
  
  
  
  1. typedef struct TrieNode  
  2. {  
  3.    char c;  
  4.    struct TrieNode* Next[26];  
  5. }TrieNode; 

8.40  如何向字典树中添加一个键

这个函数确定所传递的字符参数的整数等价值是什么。如果我们传递要确定的参数为"a",那么返回值为0;如果传递"z",那么返回值为25。

  
  
  
  
  1. int Determine(char c)  
  2. {  
  3.    int i = 0;  
  4.    char alphabet[] ={"abcdefghijklmnopqrstuvwxyz"};  
  5.    for(i=0;i<26;i++)  
  6.       if(c==alphabet[i])  
  7.          return i;  
  8. }  
  9.  
  10. void AddAKey(TrieNode *Root,char *Key)  
  11. {  
  12.    int i = 0;  
  13.    int j = 0;  
  14.    int index = 0;  
  15.    for(i=1;i<strlen(Key);i++)  
  16.    {  
  17.       index = Determine(Key[i]);  
  18.       if(Root->Next[index]==NULL)  
  19.       {  
  20.          Root->Next[index] = (TrieNode *)malloc(sizeof(TrieNode));  
  21.          Root->Next[index]->c = Key[i];  
  22.          RootRoot = Root->Next[index];  
  23.          for(j=0;j<26;j++)  
  24.             Root->Next[j] = NULL;  
  25.       }  
  26.       else  
  27.          RootRoot = Root->Next[index];  
  28.    }  

8.41  如何在字典树中搜索键

有两种方法可用来搜索树,或者向函数传递要搜索的键,或者逐个地检查字符,后一种方法对于字典树的基本用途来说更加有用。下面我们将探索后一种方法。


  
  
  
  
  1. int Search(TrieNode *Root,char *Key)  
  2. {  
  3.    enum {NOTFOUND,FOUND};  
  4.    int index = 0;  
  5.    if(Root==NULL)  
  6.       return NOTFOUND;  
  7.    else  
  8.    {  
  9.       for(int i=1;i<strlen(Key);i++)  
  10.       {  
  11.          index = Determine(Key[i]);  
  12.          if(Root->Next[index]!=NULL)  
  13.             RootRoot=Root->Next[index];  
  14.          else  
  15.             return NOTFOUND;  
  16.       }  
  17.    }  
  18.    return FOUND;  

示例8.3

编写一个程序,创建一个拥有少量单词的字典树,如图8.34所示,之后允许用户随着输入搜索键,并报告该键是否出现在字典树中。

解决方案

  
  
  
  
  1. int main()  
  2. {  
  3.    int i  = 0;  
  4.    char c;  
  5.    int flag = 0;  
  6.    TrieNode *Root=NULL, *Head = NULL;  
  7.    Root = (TrieNode *)malloc(sizeof(TrieNode));  
  8.    //这个字典树中的所有单词都以a开头  
  9.    Root->c = 'a';  
  10.    for(i=0;i<26;i++)  
  11.       Root->Next[i]=NULL;  
  12.    Head = Root;  
  13.    //在字典树中添加一些新的键  
  14.    AddAKey(Root,"admen");  
  15.    AddAKey(Root,"admix");  
  16.    AddAKey(Root,"admonish");  
  17.    AddAKey(Root,"adobe");  
  18.    AddAKey(Root,"adopt");  
  19.    AddAKey(Root,"adore");  
  20.    AddAKey(Root,"adorn");  
  21.    AddAKey(Root,"adult");  
  22.    AddAKey(Root,"adzes");  
  23.    AddAKey(Root,"aegis");  
  24.    AddAKey(Root,"aerie");  
  25.    AddAKey(Root,"affix");  
  26.  
  27.    //图8.34显示了使用这些单词创建的字典树  
  28.    printf("%d\n",Search(Head,"adze"));  
  29.    printf("%c",Root->Next[Determine('d')]->Next[Determine('m')]  
  30.             ->Next[Determine('i')]->Next[Determine('x')]->c);  
  31.    puts("\nYour Key");  
  32.    //使用键搜索字典树  
  33.    c = getche();  
  34.    if(c=='a')  
  35.    {  
  36.       for(i=0;;i++)  
  37.       {  
  38.          c = getche();  
  39.          if(c==13)  
  40.             break;  
  41.          if(Head->Next[Determine(c)]!=NULL)  
  42.          {  
  43.             HeadHead = Head->Next[Determine(c)];  
  44.             continue;  
  45.          }  
  46.          else  
  47.          {  
  48.             puts("\nNo such key");  
  49.             flag = 1;  
  50.             break;  
  51.          }  
  52.       }  
  53.       if(flag==1)  
  54.          puts("\nNo Such Key");  
  55.       else  
  56.          puts("\nFound the Key");  
  57.    }  
  58.    else  
  59.       puts("\nNo Such Key");  
  60.    getch();  
  61.    return 0;  

图8.35展示了上述程序的几次运行结果。

字典树_第2张图片 
(点击查看大图)图8.34  存储以"a"开头、少量单词的字典树
字典树_第3张图片 
图8.35  上述程序的几次运行结果

一旦键入"n",程序就报告不存在这样的键。

另一次运行如图8.36所示。

字典树_第4张图片 
图8.36  上述程序的另一次运行

"adz"是部分字符串,也就是说,它出现在字典树中。因此,搜索函数智能到即使子串出现在列表中也能找到。



8.42  如何知道字典树中的键能否被删除

为了从字典树中删除键,我们需要确定该键在字典树中是否存在,但仅仅这样还不够。即使该键在字典树中存在,我们也不一定要删除它,因为后一个结点可能是其他结点的父结点。

  
  
  
  
  1. int CanDeleteThisKey(TrieNode *Root,char *Key)  
  2. {  
  3.    int index = 0;  
  4.    int flag = 0;  
  5.    int i = 0;  
  6.    int j = 0;  
  7.    //该键在字典树中不存在,  
  8.    //因此我们不能删除它  
  9.    if(Search(Root,Key)!=1)  
  10.    {  
  11.       return 0;  
  12.    }  
  13.    else  
  14.    {  
  15.       for(i=0;i<strlen(Key);i++)  
  16.       {  
  17.          index = Determine(Key[i]);  
  18.          if(Root->Next[index]!=NULL)  
  19.             RootRoot = Root->Next[index];  
  20.          else  
  21.          {  
  22.             flag = 1;  
  23.             break;  
  24.          }  
  25.       }  
  26.       if(flag == 0)  
  27.       {  
  28.          for(j=0;j<26;j++)  
  29.          {  
  30.             if(Root->Next[j]!=NULL)  
  31.             {  
  32.                flag=1;  
  33.                break;  
  34.             }  
  35.          }  
  36.          if(flag==1)  
  37.             return 0;  
  38.          else  
  39.             return 1;  
  40.       }  
  41.    }  

8.43  如何使用字典树进行拼法检查

我们都熟悉Microsoft Word的拼写检查特性,能够使用字典树设计这个特性。

这里是一个扫描单词字典树并对错误单词找出可能正确单词的函数,错误单词只有最后一个字母错误,比如单词"Fluctuatex",如图8.37所示。

字典树_第5张图片 
图8.37  找出单词的正确拼写

  
  
  
  
  1. void DidYouMeanThese(TrieNode *Root,char    *Key)  
  2. {  
  3.    int i=0;  
  4.    int index = 0;  
  5.    char temp[25];  
  6.    strcpy(temp,Key);  
  7.    //该键不在字典树中  
  8.    if(Search(Root,Key)!=1)  
  9.    {  
  10.       for(i=0;i<strlen(Key);i++)  
  11.       {  
  12.          index = Determine(Key[i]);  
  13.          if(Root->Next[index]!=NULL)  
  14.             RootRoot = Root->Next[index];  
  15.          else  
  16.             break;  
  17.       }  
  18.       //现在我们到了这样的位置,  
  19.       //它没有孩子来补充完成这个单词  
  20.       for(int k=0;k<26;k++)  
  21.       {  
  22.          if(Root->Next[k]!=NULL)  
  23.          {  
  24.             for(i=0;i<strlen(Key)-1;i++)  
  25.                printf("%c",Key[i]); printf("%c\n",Root->Next[k]->c);  
  26.          }  
  27.       }  
  28.    }  

当函数依下述方式调用时:

  
  
  
  
  1. DidYouMeanThese(Root,"fluctuatex"); 

得到如图8.38所示的结果。

字典树_第6张图片 
图8.38  单词fluctuatex的可能正确单词

自己试一试

试着改变或增强一下上述函数,突出字符串中拼写错误的位置。



你可能感兴趣的:(字典树)