POJ 2001 Shortest Prefixes 字典树

题意很好理解就不说了,然后这道题其实不用字典树更简单,但是为了练习trie树就写了一下,1A了哈哈,再对比了一下讨论区的大神代码,发现我还是写复杂了。。。

思路

想到利用字典树,继承字典树原有机制,从底端叶子向上找,每条路径最先找

到的分叉点再往下(从叶子找上来的这条路)一个字符即为所求(特殊情况,

如果节点处单词已结束,那么就输出整个单词好了),也就是从上往下找到的

第一个不是路径重合(has_same_path = true)的情况或者是is_word =

true的情况,用遍历二叉树的方法遍历整个树得到所有前缀。
判断has_same_path的情况很简单,如果当前tmp->next[num]不为空,则

一定has_same_path
,至于pos则是为了让答案按顺序输出用的

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
poj 2001
11152K 16MS
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define MAXN 1005

using namespace std;

struct node
{
node *next[26];
bool has_same_path;
bool is_word;
int pos;
node()
{
memset(next , 0 , sizeof(next));
has_same_path = is_word = false;
pos = -1;
}
}trie[100005];
int trie_num;

struct out
{
char s[21];
int pos;
out()
{
memset(s , 0 , sizeof(s));
pos = -1;
}
}ans[MAXN];

int n = 1,id;
char str[MAXN][21],tmp_str[21];

bool cmp(out a , out b)
{
return a.pos < b.pos;
}

void insert(char *s , int pos)//插入单词
{
int len = strlen(s);
node *now = &trie[0];
for(int i = 0;i < len;i ++)
{
int num = s[i] - 'a';
if(!now->next[num])
now->next[num] = &trie[++trie_num];
else
now->next[num]->has_same_path = true;
now = now->next[num];
if(now->pos == -1)
now->pos = pos;
}
now->is_word = true;
now->pos = pos;
}

void cons(node *now , int k)//构造答案
{
if(!now->has_same_path || now->is_word)
{
tmp_str[k] = 0;
memcpy(ans[++id].s , tmp_str , k);
ans[id].pos = now->pos;
now->is_word = false;//防止回溯时再次计入
if(!now->has_same_path)
return ;
}
for(int i = 0;i < 26;i ++)
{
if(now->next[i])
{
tmp_str[k] = i + 'a';
cons(now->next[i] , k + 1);
}
}
}

int main()
{
while(scanf("%s" , str[n]) != EOF)
{
insert(str[n] , n);
n ++;
}
n --;
trie[0].has_same_path = true;
cons(&trie[0] , 0);
sort(ans + 1 , ans + n + 1 , cmp);
for(int i = 1;i <= n;i ++)
printf("%s %s\n",str[i] , ans[i].s);
return 0;
}

其实空间没必要开那么大,为了省事就乱开了

字典树

字典树,就是一种最大限度的利用单词的公共前缀高效查询单词(但不止是单词,所有有前缀的都可以类似进行查找)的数据结构。节点定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct node
{
node *next[26];//26个字母,NULL则表示没有相应字母的分支
bool isWord;//true表示从根节点到当前节点路途中所有字母连成的单词已经在字典中
int point;//指向单词的信息域(如释义等)
node()//初始化函数
{
for(int i = 0;i < 26;i ++)
next[i] = NULL;
isWord = false;
point = 0;
}
}

建树和查询都非常方便,删除操作并不常用,比较偷懒的方法就是找到待删除单词的最后一个字母所在节点,把isWord标记置为false即可。代码就不给出了。

需要注意的是,在实际建树过程中,如果动态分配空间无论是new还是malloc都会很慢,一般都会TLE的,所以代码都是静态实现,即先分配一定的空间,需要用新节点时就从空间里拿。

例题:

一、POJ 2503 Babelfish

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
poj 2503
17664K 235MS
*/
#include<cstdio>
#include<cstring>
#include<iostream>

#define MAXN 150005

using namespace std;

struct node
{
node *next[26];
bool isWord;
int point;//不在结构体内设置字符数组而是模拟一个指针指向储存区这样可以节省不少空间
node()
{
for(int i = 0;i < 26;i ++)
next[i] = NULL;
isWord = false;
point = 0;
}
}all[MAXN];

int index = 1,size;
char store[100005][11];

void insert(char *str1,char *str2)
{
node *tmp = &all[0];
int len = strlen(str1);
for(int i = 0;i < len;i ++)
{
int num = str1[i] - 'a';
if(!tmp->next[num])
tmp->next[num] = &all[index++];
tmp = tmp->next[num];
}
tmp->isWord = true;
size ++;
tmp->point = size;
strcpy(store[size] , str2);
}

void find(char *s)
{
node *tmp = &all[0];
int len = strlen(s);
for(int i = 0;i < len;i ++)
{
int num = s[i] - 'a';
if(!tmp->next[num])
{
printf("eh\n");
return ;
}
tmp = tmp->next[num];
}
if(tmp->isWord)
printf("%s\n",store[tmp->point]);
else
printf("eh\n");
}

int main()
{
char str1[12] = {0},str2[12] = {0},s[24];
while(gets(s) && s[0])
{
sscanf(s,"%s %s",str1,str2);
insert(str2,str1);
}
while(scanf("%s",s) != EOF)
{
find(s);
}
return 0;
}

二、POJ 3630、Phone List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/*
poj 3630
4628K 219MS
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define MAXN 100005

using namespace std;

struct alp
{
char s[11];
}per[MAXN / 10 + 5];

struct node
{
bool isWord;
node *next[10];
node()//初始化函数很重要!
{
isWord = false;
for(int i = 0;i < 10;i ++)
next[i] = NULL;
}
}all[MAXN];

bool cmp(alp a,alp b)
{
return strlen(a.s) < strlen(b.s);
}

void doIt()
{
memset(all , 0 , sizeof(all));
int n,index = 0;
cin>>n; node *root, *tmp;
root = &all[index++];
for(int i = 1;i <= n;i ++)
scanf("%s",per[i].s);
sort(per + 1 , per + n + 1 , cmp);
//要先按字符串长度从小到大排序,不然对于 12 1之类的数据会出错
for(int p = 1;p <= n;p ++)
{
tmp = root;
int len = strlen(per[p].s);
for(int i = 0;i < len;i ++)
{
int num = per[p].s[i] - '0';
if(tmp->next[num])
{
if(tmp->next[num]->isWord)
{
printf("NO\n");
return ;
}
}
else
tmp->next[num] = &all[index++];
tmp = tmp->next[num];
}
tmp->isWord = true;
}
printf("YES\n");
return ;
}

int main()
{
int T;
cin>>T;
while(T --)
{
doIt();
}
return 0;
}