【資料結構與演算法】字典樹(附完整原始碼)
字典樹簡介
字典書(Trie Tree),又稱單詞查詢樹,是鍵樹的一種,典型應用是用於統計,排序和儲存大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。
字典樹有3個基本性質:
1、根節點不包含字元,其餘的每個節點都包含一個字元;
2、從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串;
3、每個節點的所有子節點包含的字元都不相同。
字典樹的結構一般如下圖所示:
以字串為例,我們可以將其儲存結構寫成如下形式:
這裡,因為我們用的字符集為26個小寫字母,因此MAX為26。可以將此資料儲存結構與上面的儲存圖例相比較,以加深理解。這裡很容易看出,字典樹是典型的用空間換時間,儲存空間極大,這在海量字串時很適用。#define MAX 26 //字符集的大小 /* 字典樹的儲存結構 */ typedef struct Node { struct Node *next[MAX]; //每個節點下面可能有MAX個字元 int count; //以從根節點到當前節點的字串為公共字首的字串數目 }TrieNode,*TrieTree;
字典樹有如下2個優點:
1.利用字串的公共字首來節約儲存空間。
2.最大限度地減少無謂的字串比較,查詢效率比較高。
字典樹的應用
字典樹的應用很廣泛,下面是摘自百度百科的3個應用介紹:
1、在串快速檢錯中的應用
字典樹的應用很廣泛,下面是摘自百度百科的3個應用簡介:
1、字典樹在串的快速檢索中的應用。
給出N個單片語成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最早出現的順序寫出所有不在熟詞表中的生詞。在這道題中,我們可以用字典樹,先把熟詞建一棵樹,然後讀入文章進行比較,這種方法效率是比較高的。
2、字典樹在“串”排序方面的應用
給定N個互不相同的僅由一個單詞構成的英文名,讓你將他們按字典序從小到大輸出用字典樹進行排序,採用陣列的方式建立字典樹,這棵樹的每個結點的所有兒子很顯然地按照其字母大小排序。對這棵樹進行先序遍歷即可。
3、字典樹在最長公共字首問題的應用
對所有串建立字典樹,對於兩個串的最長公共字首的長度即他們所在的結點的公共祖先個數,於是,問題就轉化為最近公共祖先問題。
求字串的公共字首
下面我們就以求字串的公共字首為例,來具體說明字典樹的應用。
在字典樹中,主要的操作是查詢,我們可以通過不斷地插入來建造字典樹,但是刪除操作用到的很少。
首先是儲存結構,如前面的程式碼所示,其中:
next是表示每層有多少種類的數,如果只是小寫字母,則26即可,若改為大小寫字母,則是52,若再加上數字,則是62了,這裡根據題意來確定。
count表示以從根節點到該節點的字串為公共字首的字串的數目。我們用它來來記錄每個節點在詞典中出現的次數,然後在對應的插入操作(Insert)中,每次插入時經過的節點作count++操作;這樣進行搜尋(Search)時,若搜尋成功只需要返回搜尋終止節點的count變數的值,就是以某個字串為字首的單詞總數。
如果明白了字典樹的儲存,下面的操作思想也就一目瞭然了,插入和查詢都是從根節點依次向下,而字典樹的銷燬則通過遞迴的思想來實現。我這裡直接貼出所有操作(插入、查詢、銷燬等)的程式碼:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"data_structure.h"
/*
建立一棵僅有根節點的字典樹
*/
TrieTree create_TrieTree()
{
TrieTree pTree = (TrieTree)malloc(sizeof(TrieNode));
pTree->count = 0;
int i;
for(i=0;i<MAX;i++)
pTree->next[i] = NULL;
return pTree;
}
/*
插入字串str到字典樹pTree中
由於不可能改變根節點,因此這裡不需要傳入pTree的引用
*/
void insert_TrieTree(TrieTree pTree,char *str)
{
int i;
TrieTree pCur = pTree; //當前訪問的節點,初始指向根節點
int len = strlen(str);
for(i=0;i<len;i++)
{
int id = str[i] - 'a'; //定位到字元應該在的位置
if(!pCur->next[id])
{ //如果該字元應該在的位置為空,則將字元插入到該位置
int j;
TrieNode *pNew = (TrieNode *)malloc(sizeof(TrieNode));
if(!pNew)
{
printf("pNew malloc fail\n");
exit(-1);
}
pNew->count = 1;
for(j=0;j<MAX;j++)
pNew->next[j] = NULL;
//指標後移,比較下一個字元
pCur->next[id] = pNew;
pCur = pCur->next[id];
}
else
{ //如果該字元應該在的位置不空,則繼續比較下一個字元
pCur = pCur->next[id];
pCur->count++; //每插入一個字元,公共字首數目就加1
}
}
}
/*
統計字典樹pTree中以str為字首的字串的數目
*/
int count_TrieTree(TrieTree pTree,char *str)
{
int i;
TrieTree pCur = pTree;
int len = strlen(str);
for(i=0;i<len;i++)
{
int id = str[i] - 'a';
if(!pCur->next[id])
return 0;
else
pCur = pCur->next[id];
}
return pCur->count;
}
/*
銷燬字典樹
*/
void destroy_TrieTree(TrieTree pTree)
{
int i;
//遞迴釋放每個節點的記憶體
for(i=0;i<MAX;i++)
if(pTree->next[i])
destroy_TrieTree(pTree->next[i]);
free(pTree);
}
測試執行結果如下: