关于学习字典树,可以转步这篇文章传送门
例题1、poj3630 / hdu 1671
这里要在插入时候进行检验是否存在某个号码是已存在号码的前缀或者存在某个号码是当前插入号码
的前缀。
先说hdu1671,因为poj的使用动态建树给tle了,所以要用静态建树,这里先说动态建树
hdu1671 代码(感觉可以做模板)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <string>
#include <sstream>
using namespace std;
const int Size = 10;
struct Trie
{
int count;//前缀出现次数;
Trie *next[Size];//孩子节点数目
bool isEnd;//判断到该点时,是否是一个单词
//string name;
};
void init(Trie *node)
{
node->count = 0;
memset(node->next,NULL,sizeof node->next);
node->isEnd = false;
}
Trie *root = new Trie;//建立根节点;
bool Insert(char s[])
{
int len = strlen(s);
int pos;
Trie *p = root;
for(int i = 0; i < len; i++){
pos = s[i] - '0';
if(p->next[pos] == NULL){
//如果这个字符在这个节点没有储存过,就建立一个新节点来储存
p->next[pos] = new Trie;
init(p->next[pos]);
}
p = p->next[pos];
p->count++;
if(p->count > 1&&p->isEnd){
//如果到这个点为止,已经走过两次及以上
//并且当前位置是一个单词
//即表示,有一个完整的电话号码是当前插入号码的前缀串,直接返回false
return false;
}
}
p->isEnd = true;//表明当前位置为一个单词的节点
if(p->count > 1&&p->isEnd){
//理由同上
return false;
}
return true;
}
int Search(char s[])
{
Trie *p = root;
int len = strlen(s);
for(int i = 0; i < len; i++){
int pos = s[i] - '0';
if(p->next[pos] == NULL){
return 0;
}
p = p->next[pos];
}
return p->count;//如果经过这里的次数 > 0就说明这个单词存在
}
//清除Trie
void del(Trie *root,int mark)
{
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
del(root->next[i],mark + 1);
}
}
if(mark){
//防止将根节点释放
delete root;
}
}
//遍历
/*void Print(Trie *root)
{
if(root->isEnd){
cout<<root->name<<':'<<root->count<<endl;
}
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
Print(root->next[i]);
}
}
}*/
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n;
bool flag = true;//刚开始不可能有不满足条件的情况
scanf("%d",&n);
char s[20];
init(root);
while(n--){
scanf("%s",s);
if(flag){
if(!Insert(s)){
flag = false;
}
}
}
if(flag){
printf("YES\n");
}
else{
printf("NO\n");
}
del(root,0);//释放树,下次再建立
}
return 0;
}
下面说一下静态建树
其实就是把空间提前分配好,然后把分配好空间的地址在程序需要往下建树的时候传过去就可以,就是多了一个静态数组和一个传递当前数组元素地址的函数
剩下的跟动态建树一样操作
代码如下
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <string>
#include <sstream>
using namespace std;
const int Size = 10;
const int MAXN = 100000+999;
struct Trie
{
int count;//前缀出现次数;
Trie *next[Size];//孩子节点数目
bool isEnd;//判断到该点时,是否是一个单词
//string name;
}trie[MAXN];
int cnt = 0;
//传递数组元素地址,算是模拟分配空间吧,只不过是空间提前分配好了,把地址传进去而已
Trie *Newnode()
{
Trie *node = &trie[cnt];
cnt++;
return node;
}
void init(Trie *node)
{
node->count = 0;
memset(node->next,NULL,sizeof node->next);
node->isEnd = false;
}
Trie *root = Newnode();//建立根节点;
bool Insert(char s[])
{
int len = strlen(s);
int pos;
Trie *p = root;
for(int i = 0; i < len; i++){
pos = s[i] - '0';
if(p->next[pos] == NULL){
//如果这个字符在这个节点没有储存过,就建立一个新节点来储存
p->next[pos] = Newnode();
init(p->next[pos]);
}
p = p->next[pos];
p->count++;
if(p->count > 1&&p->isEnd){
//如果到这个点为止,已经走过两次及以上
//并且当前位置是一个单词
//即表示,有一个完整的电话号码是当前插入号码的前缀串,直接返回false
return false;
}
}
p->isEnd = true;//表明当前位置为一个单词的节点
if(p->count > 1&&p->isEnd){
//理由同上
return false;
}
return true;
}
int Search(char s[])
{
Trie *p = root;
int len = strlen(s);
for(int i = 0; i < len; i++){
int pos = s[i] - '0';
if(p->next[pos] == NULL){
return 0;
}
p = p->next[pos];
}
return p->count;//如果经过这里的次数 > 0就说明这个单词存在
}
//清除Trie
void del(Trie *root,int mark)
{
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
del(root->next[i],mark + 1);
}
}
if(mark){
//防止将根节点释放
delete root;
}
}
//遍历
/*void Print(Trie *root)
{
if(root->isEnd){
cout<<root->name<<':'<<root->count<<endl;
}
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
Print(root->next[i]);
}
}
}*/
int main()
{
int t;
scanf("%d",&t);
while(t--){
int n;
cnt = 0;
bool flag = true;//刚开始不可能有不满足条件的情况
scanf("%d",&n);
char s[20];
root = Newnode();//初始化
init(root);
while(n--){
scanf("%s",s);
if(flag){
if(!Insert(s)){
flag = false;
}
}
}
if(flag){
printf("YES\n");
}
else{
printf("NO\n");
}
//del(root,0);//释放树,下次再建立
memset(trie,0,sizeof trie);//将空间清0
}
return 0;
}
2、poj2001
就不说题意了
就是先把树建好,设置一个count记录前缀被经历了几次,如果出现一次,那么就直接把这个前缀输出,反之就继续向下输出前缀,最坏的结果就是把整个字符串输出。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <string>
#include <sstream>
using namespace std;
const int Size = 26;
const int MAXM = 100000+999;
const int MAXN = 100005;
struct Trie
{
int count;//前缀出现次数;
Trie *next[Size];//孩子节点数目
bool isEnd;//判断到该点时,是否是一个单词
//string name;
}trie[MAXM];
int cnt;
char s[1005][25];
//传递数组元素地址,算是模拟分配空间吧,只不过是空间提前分配好了,把地址传进去而已
Trie *Newnode()
{
Trie *node = &trie[cnt];
cnt++;
return node;
}
Trie *root = Newnode();//建立根节点;
void init(Trie *node)
{
node->count = 0;
memset(node->next,NULL,sizeof node->next);
node->isEnd = false;
}
void Insert(const char *s)
{
int len = strlen(s);
int pos;
Trie *p = root;
for(int i = 0; i < len; i++){
pos = s[i] - 'a';
if(p->next[pos] == NULL){
//如果这个字符在这个节点没有储存过,就建立一个新节点来储存
p->next[pos] = Newnode();
init(p->next[pos]);
}
p = p->next[pos];
p->count++;
}
p->isEnd = true;//表明当前位置为一个单词的节点
}
int Search(const char *s)
{
Trie *p = root;
int len = strlen(s);
for(int i = 0; i < len; i++){
int pos = s[i] - 'a';
if(p->next[pos] == NULL){
return 0;
}
p = p->next[pos];
}
return p->count;//如果经过这里的次数 > 0就说明这个单词存在
}
//清除Trie
void del(Trie *root,int mark)
{
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
del(root->next[i],mark + 1);
}
}
if(mark){
//防止将根节点释放
delete root;
}
}
//遍历
/*void Print(Trie *root)
{
if(root->isEnd){
cout<<root->name<<':'<<root->count<<endl;
}
for(int i = 0; i < Size; i++){
if(root->next[i] != NULL){
Print(root->next[i]);
}
}
}*/
void Output(const char *s)
{
Trie *p = root;
int len = strlen(s);
int pos;
for(int i = 0; i < len; i++){
pos = s[i] - 'a';
printf("%c",s[i]);
if(p->next[pos]->count == 1){
return ;
}
p = p->next[pos];
}
}
int main()
{
int cot = 0;
while(scanf("%s",s[cot]) == 1){
Insert(s[cot]);
cot++;
//if(cot == 12) break;
}
for(int i = 0; i < cot; i++){
printf("%s ",s[i]);
Output(s[i]);
printf("\n");
}
return 0;
}