數據結構與算法系列----字典樹


一:背景

什么是字典樹?

Trie樹,即字典樹,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。Trie的核心思想是空間換時間。利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。

假如給出一些單詞,and,as,at,cn,com,構建下面的字典樹:


從上圖可以發現:

它有3個基本性質:
1.根節點不包含字符,除根節點外每一個節點都只包含一個字符。
2.從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串。
3.每個節點的所有子節點包含的字符都不相同。

你可能會想,這有什么用?

第一:詞頻統計
可能有人要說了,詞頻統計簡單啊,一個hash或者一個堆就可以打完收工,但問題來了,如果內存有限呢?還能這么玩嗎?所以這里我們就可以用trie樹來壓縮下空間,因為公共前綴都是用一個節點保存的。

第二: 前綴匹配
就拿上面的圖來說吧,如果我想獲取所有以"a"開頭的字符串,從圖中可以很明顯的看到是:and,as,at,如果不用trie樹,你該怎么做呢?很顯然朴素的做法時間復雜度為O(N2) ,那么用Trie樹就不一樣了,它可以做到h,h為你檢索單詞的長度,可以說這是秒殺的效果。


二:完整代碼

#define _CRT_SECURE_NO_DEPRECATE   
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1

#include<iostream>
#define MAX 26//假設字符只出現 abc..k..xyz 26個小寫英文字母

using namespace std;

struct Node
{
int num;
Node * next[MAX];
Node()
{
num = 0;

for (int i = 0; i < MAX; i++)
next[i] = nullptr;
}
};

class Trie
{
public:
Node *root;
Trie() { root = new Node; }

void Add(Node *node, const char *ch);
int Find(const char *ch);
void Delete(Node *node, const char *ch);
};

void Trie::Add(Node *node, const char *ch)
{
int len = strlen(ch);
if (len == 0)
return;

int order = *ch - 'a';//得到字符的位置

if (node->next[order] == nullptr)
node->next[order] = new Node;

len = strlen(ch + 1);
if (len == 0)//說明是一個單詞的結尾,需要將該單詞出現的次數加一
{
node->next[order]->num++;
return;//下面的遞歸可以結束了
}

Add(node->next[order], ch + 1);//遞歸下去
}

int Trie::Find(const char *ch)
{
Node *p = root;

while (*ch != '\0')
{
int order = *ch - 'a';
if (p->next[order] == nullptr)
return 0;
else
{
ch++;
p = p->next[order];
}
}

return p->num;
}

void Trie::Delete(Node *node, const char *ch)
{
int len = strlen(ch);
if (len == 0)
return;

int order = *ch - 'a';
if (node->next[order] == nullptr)
return;

len = strlen(ch + 1);
if (len == 0 && node->next[order]->num != 0)//找到該單詞並且該單詞出現次數大於0
{
node->next[order]->num--;
return;
}

Delete(node->next[order], ch + 1);
}

int main()
{
Trie tree;

tree.Add(tree.root, "strawberry");
tree.Add(tree.root, "grandfather");
tree.Add(tree.root, "policeman");
tree.Add(tree.root, "breakfast");
tree.Add(tree.root, "mutton");
tree.Add(tree.root, "bus");
tree.Add(tree.root, "bus");
tree.Add(tree.root, "computer");

int k = tree.Find("bus");
cout << k << endl;//2

tree.Delete(tree.root, "bus");
cout << tree.Find("bus") << endl;//1

return 0;
}

三:總結

細心的朋友會發現,上面的代碼有缺陷,拿Delete操作來說吧,if (len == 0 && node->next[order]->num != 0),這句代碼其實是有問題的,因為我沒有考慮node->next[order]->num==1的情況,如果等於1,現在執行刪除,自減1,num就是0了,也就是這個單詞從來沒出現,那么表示這個 單詞這一條路徑都要delete(如果這條路徑上不存在其他的單詞),並且root處的指針需要reset。

好了,雖然代碼是有缺陷的,但是只要把實現搞懂,該如何進行完善那就是看個人的需求了。





返回目錄---->數據結構與算法目錄







另外推薦讀者下面這篇博客: http://blog.csdn.net/v_july_v/article/details/6897097,JULY大牛

圖片資源和代碼參考:http://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com