Trie,又稱字典樹、單詞查找樹,是一種樹形結構,用於保存大量的字符串。其核心思想就是空間換時間。
優點是:利用字符串的公共前綴來節約存儲空間。
有3個基本性質:
1. 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
2. 從根節點到某一節點,路徑上經過的字符連接起來,爲該節點對應的字符串。
3. 每個節點的所有子節點包含的字符都不相同。
舉個簡單的例子。
給你100000個長度不超過10的單詞。對於每一個單詞,我們要判斷他出沒出現過,如果出現了,第一次出現第幾個位置。
這題可以用hash來,但是trie樹在某些方面它的用途更大。比如說對於某一個單詞,要詢問它的前綴是否出現過。這樣hash就不如trie。回到例子中,如果用最傻的方法,對於每一個單詞,我們都要去查找它前面的單詞中是否有它。那麼這個算法的複雜度就是O(n^2)。顯然對於100000的範圍難以接受。現在我們換個思路,假設要查詢的單詞是abcd,那麼在他前面的單詞中,以b,c,d,f之類開頭的我顯然不必考慮。而只要找以a開頭的中是否存在abcd就可以了。同樣的,在以a開頭中的單詞中,我們只要考慮以b作爲第二個字母的……這樣一個樹的模型就漸漸清晰了……
假設有b,abc,abd,bcd,abcd,efg,hii這6個單詞,構建的樹就是這樣的。
對於每一個節點,從根遍歷到他的過程就是一個單詞,如果這個節點被標記爲紅色,就表示這個單詞存在,否則不存在。
那麼,對於一個單詞,只要順着他從跟走到對應的節點,再看這個節點是否被標記爲紅色就可以知道它是否出現過了。把這個節點標記爲紅色,就相當於插入了這個單詞。這樣詢問和插入一起完成,所用時間僅僅爲單詞長度,在這一個樣例,便是10。
trie樹每一層的節點數是26^i級別的。爲了節省空間,用動態鏈表,或者用數組來存儲樹結構。空間的花費,不會超過單詞數×單詞長度。
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int num_chars = 26;
class Trie {
public:
Trie():root(NULL){};
Trie(Trie& tr);
int search(const char* word, char* entry ) const;
int insert(const char* word, const char* entry);
int remove(const char* word, char* entry);
private:
struct Trie_node
{
char* data;
Trie_node* branch[num_chars];
Trie_node();
}* root;
};
Trie::Trie_node::Trie_node()
{
data = NULL;
for (int i=0; i<num_chars; ++i)
branch[i] = NULL;
}
int 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);
}
return result;
}
int main()
{
Trie t;
char entry[100];
t.insert("aa", "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.search("this", entry))
cout<<"'this' was found. pos: "<<entry<<endl;
if (t.search("abate", entry))
cout<<"'abate' is found. pos: "<<entry<<endl;
if (t.search("baby", entry))
cout<<"'baby' is found. pos: "<<entry<<endl;
else
cout<<"'baby' does not exist at all!"<<endl;
if (t.search("aa", entry))
cout<<"'aa was found. pos: "<<entry<<endl;
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
//結點類型
typedef struct node
...{
bool isStr; //記錄此處是否有串
int count; //記錄子結點的個數
node* next[26]; //孩子的指針數組,數組最多爲26,實際上爲了減小空間,可以將這裏定義爲node **,他的大小結合count動態擴張
} node()
: count(0), isStr(false)
...{
memset(next, NULL, sizeof(next)); //初始化爲空
}
} *nodeptr;
//Trie類
//insert:插入一個字符串,重複插入無效
//remove:刪除指定的字符串,如果不存在,則不進行操作
//find:判斷是否有指定的字符串
class Trie
...{
private:
nodeptr root;
//刪除t的孩子的孩子
//這樣做的原因是
//delete一個指針,這個指針就不確定了,不能繼續向上操作
//所以只能刪除t的孩子的孩子
//有待改進
void remove(nodeptr t, const char* key, int i)
...{
//孩子的指針,key[i]爲孩子
nodeptr pnext = t -> next[key[i] - 'a'];
if (key[i+1] && pnext) //不是最後一個結點
...{
pnext -> count--;
remove(pnext, key, i+1); //遞歸刪除
}
if (!pnext -> count) //子結點是空的,直接刪除
delete pnext;
else //否則刪除子結點的孩子的指針
pnext -> next[key[i+1] - 'a'] = NULL;
}
public:
Trie()
...{
root = new node;
}
//插入操作
void insert(const char* key)
...{
nodeptr location = root;
do
...{
if (location -> next[*key - 'a'] == NULL) //不存在則新建
...{
nodeptr tmp = new node;
location -> next[*key - 'a'] = tmp;
}
if (*key) //不是0
...{
location -> count++;
location = location -> next[*key - 'a'];
}
} while (*key++);
location -> isStr = true; //到達尾部
}
//刪除操作
void remove(const char* key)
...{
if (!find(key)) //找不到則不操作
return;
//預處理根
root -> count--;
//刪除根的子結點
remove(root, key, 0);
//尾處理
if (!root -> next[*key - 'a'] -> count)
delete root -> next[*key - 'a'];
else
root -> next[*key - 'a'] = NULL;
}
//查找
bool find(const char* key)
...{
nodeptr location = root;
while (*key && location)
location = location -> next[*key++ - 'a'];
return (location != NULL && location -> isStr);
}
};
文章出處:http://www.diybl.com/course/6_system/linux/Linuxjs/20071027/80149.html
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
字典樹的第一題
#include<stdio.h>
#include<string.h>
#include<iostream.h>
#define MAX_NUM 26
#define MAX_DATA 100005
class Tire
{
public:
Tire();
int TireSearch(const char *word,char *entry);
void TireInsert(const char *word,const char *entry);
protected:
struct TireNode
{
char*data;
TireNode* branch[MAX_NUM];
TireNode();
};
TireNode *root;
};
Tire::TireNode::TireNode()
{
data=NULL;
int i;
for(i=0;i<MAX_NUM;i++)
branch[i]=NULL;
}
{
root=NULL;
}
{
int position=0;
if(root==NULL)
root=new TireNode;
char char_code;
TireNode *location=root;
while(location!=NULL&&*word!=0)
{
if(*word>='a'&&*word<='z')
char_code=*word-'a';
if(location->branch[char_code]==NULL)
location->branch[char_code]=new TireNode;
location=location->branch[char_code];
position++;word++;
location->data=new char[strlen(entry)+1];
strcpy(location->data,entry);
}
{
int position=0;
char char_code;
TireNode *location=root;
while(location!=NULL&&*word!=0)
{
if(*word>='a'&&*word<='z')
char_code=*word-'a';
location=location->branch[char_code];
position++;
word++;
}
if(location!=NULL&&location->data!=NULL)
{
strcpy(entry,location->data);
return 1;
}
else
return 0;
}
Tire T;
int main()
{
char str1[10],str2[10],str3[10];
char str[100];
char entry[10];
char ch;
int i,j;
while(1)
{
j=0;
gets(str);
if(strlen(str)==0)
{
// printf("fjkdsajkfdjk/n");
break;
}
for(i=0;str[i]!=' ';i++)
str1[i]=str[i];
str1[i]='/0';
for(i++;i<strlen(str);i++)
str2[j++]=str[i];
str2[j]='/0';
T.TireInsert(str2,str1);
}
while(scanf("%s",str3)!=EOF)
{
//gets(str3);
//getchar();
if(str3[0]=='/0')
break;
if(T.TireSearch(str3,entry))
printf("%s/n",entry);
else
printf("eh/n");
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
算法描述爲:由字母a~z所組成的字符串的一個集合中,各個字符的長度之和爲n。設計一個O(n)時間的算法,將這個集合中所有字符串依字典進行排序。注意,這裏可能存在非常長的字符串。
#include <stdio.h>
#include <malloc.h>
typedef struct tire
{
struct tire *next[26];
char date;
int cnt;
}*_tire;
void init_tire(_tire root, char *string)
{
_tire s;
s=root;
while(*string!=’//0’)
{
if(s->next[*string - ’a’]==NULL)
{
s->next[*string - ’a’] = (_tire)malloc(sizeof(struct tire));
(s->next[*string - ’a’])->date = *string;
s = s->next[*string - ’a’];
for(int i=0;i<26;i++)
{
s->next[i] = NULL;
}
}
else
{
s = s->next[*string - ’a’];
}
string++;
}
s->cnt=1;
}
void print(_tire root, char *s, int i)
{
int j;
s[i] = root->date;
if(root->cnt==1)
{
s[i+1] = 0;
puts(s);
}
for(j=0;j<26;j++)
{
if(root->next[j]!=NULL)
{
print(root->next[j],s,i+1);
}
} [Page]
}
int main()
{
_tire root;
int m,i;
char s[265];
root = (_tire)malloc(sizeof(struct tire));
puts(/"輸入字符串個數:/");
for(i=0;i<26;i++)
{
root->next[i]=NULL;
}
scanf(/"%d/",&m);
getchar();
while(m--)
{
gets(s);
init_tire(root,s);
}
puts(/"//n依字典排序後:/");
for(i=0;i<26;i++)
{
if(root->next[i] != NULL)
{
print(root->next[i],s,0);
}
}
return 0;
}