最近重看了下KMP,然后学习AC自动机、DFA(Trie图),但发现还没有做过Trie树的题目。于是做了四五题简单的Trie树。
Tire树,发音“Try树”,也就是字典树。建立和查询的方式,类似我们建造字典和查询字典。
比如she he her his share
查找her,我们会找到h开头的那部分,然后找到he开头的部分,最后找到这个单词her。当然,建立字典也是类似的,只是不是查看而是插入。
通过四五道练习,不仅仅熟悉了Trie树,更重要的是发现实现Trie树,用动态分配内存和静态分配内存性能上显著不同
当然,结论是,静态分配会高效很多,但用了一些全局变量,不熟悉的情况下容易出错。熟悉了就没问题了。
动态分配,对于有多个测试实例,如果不释放动态分配的内存,可能导致MLE!比如这题:http://acm.hdu.edu.cn/showproblem.php?pid=1671
当然,测试实例不多,不释放也没有问题,比这题:http://poj.org/problem?id=2945
但是,释放动态分配的内存,一般就是DFS释放整棵树,还是比较消耗时间的。
比如poj2945——
动态分配&&不释放内存 ——内存消耗:54460K 时间消耗:2204MS
动态分配&&每次释放内存——内存消耗: 7932K 时间消耗:3672MS
静态分配 ——内存消耗: 4548K 时间消耗: 532MS
下面给出poj2945的Trie树解法 及其 动态分配实现、静态分配实现
代码比较搓,先这样吧,以后再修改
静态分配实现——
//poj2945
//Trie树解法,静态分配实现
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int const childnum = 4;
const int map[] = {
0, -1, 1, -1, -1, -1, 2,
-1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 3
};
struct node {
node* child[childnum];
int times;
bool isstop;
};
const int peoplenum = 20010;
const int DNAlen = 21;
node Tree[peoplenum*DNAlen];
int nodecnt = 0;
int rec[peoplenum]; //若rec[9] = 3 表示重复了9次的不同基因有3个
char str[peoplenum][DNAlen];
//内联会稍微快点
inline void init(node *p)
{
memset(p->child, NULL, sizeof (p->child));
p->isstop = false;
p->times = 0;
}
void insert(node *p, char *s)
{
int rec = nodecnt;
for (int i = 0; s[i]; i++) {
int idx = map[s[i]-'A'];
if (p->child[idx] == NULL) {
nodecnt++; //notice!!!
p->child[idx] = Tree + nodecnt;
init(p->child[idx]); //notice!!!
}
p = p->child[idx];
}
if (p->isstop == true) {
(p->times++);
return;
}
p->isstop = true;
p->times = 1;
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m), n || m) {
node *p = Tree; //notice!!!
init(p); //notice!!!
nodecnt = 0; //notice!!!
for (int i = 0; i != n; i++) {
scanf("%s", str[i]);
insert(p, str[i]);
}
memset(rec, 0, sizeof (rec));
for (int i = 1; i != nodecnt+1; i++) {
if (Tree[i].isstop = true) {
rec[Tree[i].times]++;
}
}
for (int i = 1; i != n+1; i++) {
printf("%d\n", rec[i]);
}
}
return 0;
}
动态分配——
//poj2945
//Trie树解法——动态分配实现
//数据量明确,完全可以用静态分配空间,但每个测试实例要记得初始化原来的树(树根和新结点)
//数据量不明确,则动态分配空间,每个测试实例的Trie树用完后要释放空间,否则可能MLE
//Trie树的时间消耗——
//静态建树更快,动态比较慢
//Trie树的初始化——
//都不能在旧树上继续建立,注意主函数,每个实例要在一个空树上(child[i]全为NULL)开始
//Trie树的空间消耗——
//静态建树可以始终Tree数组上建立(但注意全局变量nodecnt,每个结点的初始化)
//动态建树如果不free掉旧树,可能MLE,但是free掉旧树会消耗相当一部分时间!
//结论是静态建树好,但是如果每个实例大小不清楚呢?根据题目的内存限制把数组尽量开大!
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int const childnum = 4;
const int map[] = {
0, -1, 1, -1, -1, -1, 2,
-1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 3
};
struct node {
node* child[childnum];
int times;
bool isstop;
node(): times(0), isstop(false) {
memset(child, NULL, sizeof (child));
}
};
const int peoplenum = 20010;
const int DNAlen = 21;
int rec[peoplenum];
char str[peoplenum][DNAlen];
node* save_stop_node[peoplenum];
int k = 0; //index for save_stop_node
void free(node *r)
{
for (int i = 0; i != childnum; i++) {
if (r->child[i] != NULL)
free(r->child[i]);
}
delete r;
}
void insert(node *p, char *s)
{
for (int i = 0; s[i]; i++) {
int idx = map[s[i]-'A'];
if (p->child[idx] == NULL) {
p->child[idx] = new node;
}
p = p->child[idx];
}
if (p->isstop == true) {
(p->times++);
return;
}
p->isstop = true;
save_stop_node[k++] = p;
p->times = 1;
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m), n || m) {
node *r = new node;
for (int i = 0; i != n; i++) {
scanf("%s", str[i]);
insert(r, str[i]);
}
memset(rec, 0, sizeof (rec));
for (int i = 0; i != k; i++)
rec[save_stop_node[i]->times]++;
for (int i = 1; i != n+1; i++) {
printf("%d\n", rec[i]);
}
free(r); //POJ2945,把free(r)注释掉运行更快,但内存消耗变大,还好不会MLE
k = 0;
}
return 0;
}
再附几个题解
hdoj1671
//hdoj1671 Phone List
//2013-5-21
//静态分配
//性能:2684K 78MS
#include <iostream>
#include <cstdio>
#include <cstring>
int const childnum = 10;
struct node {
node *child[childnum];
bool isstop;
};
inline void init(node *p)
{
memset(p->child, NULL, sizeof (p->child));
p->isstop = false;
}
int nodecnt = 0; //统计当前实例Tree树除树根的结点个数
const int size = 1000000;
node Tree[size];
int insert(node *p, char* s)
{
int rec = nodecnt;
for (int i = 0; s[i] != '\0'; i++) {
int idx = s[i]-'0';
if (p->child[idx] == NULL) {
if (p->isstop) //ex. 911 91152
return 0;
nodecnt++;
p->child[idx] = Tree + nodecnt;
init(p->child[idx]);//每个结点都要初始化
}
p = p->child[idx];
}
p->isstop = true;
return nodecnt == rec ? 0 : 1; //ex. 91152 911
}
int main()
{
int t, n, flag;
char number[15];
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
flag = 1;
node *r = Tree;
nodecnt = 0; //记得每次新树建前nodecnt清零
init(Tree); //记得每次给树根初始化
while (n--) {
scanf("%s", number);
if (flag == 1)
flag = insert(r, number);
}
printf(flag == 1 ? "YES\n" : "NO\n");
}
return 0;
}
hdoj1277
#include <iostream>
#include <cstdio>
using namespace std;
const int numsize = 10;
struct node {
node *child[numsize];
int num;
};
int nodecnt = 0;
node Tree[1000000];
inline void init(node *p)
{
memset(p->child, NULL, sizeof (p->child));
p->num = -1;
}
void insert(node* p, char *s, int count)
{
for (int i = 0; s[i] != '\0'; i++) {
int idx = s[i]-'0';
if (p->child[idx] == NULL) {
nodecnt++;
p->child[idx] = Tree + nodecnt;
init(p->child[idx]);
}
p = p->child[idx];
}
p->num = count;
}
int mark[10010];
void search(node *r, char *s)
{
node *p = r;
for (int i = 0; s[i] != '\0'; i++) {
int idx = s[i]-'0';
if (p->num != -1 && mark[p->num] == 0) {
printf(" [Key No. %d]", p->num);
mark[p->num] = 1;
}
if (p->child[idx] == NULL)
return;
p = p->child[idx];
}
}
char Text[60010];
char tmp[10];
char key[65];
int main()
{
int n, m;
int cnt = 0, k = 0;
char c;
scanf("%d%d%*c", &n, &m);
getchar();
while (c = getchar()) {
if (c == '\n')
cnt++;
else
Text[k++] = c;
if (cnt == n)
break;
}
Text[k] = '\0';
node *r = Tree;
init(r);
nodecnt = 0;
for (int i = 1; i != m+1; i++) {
scanf("%s%s%s%s", tmp, tmp, tmp, key);
insert(r, key, i);
}
printf("Found key:");
for(int i=0; Text[i] != '\0'; i++) {
search(r, Text+i);
}
printf("\n");
return 0;
}
另外这个题目的测试数据全都是可以找到key的!