您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法之字典树解题

2013-11-30 02:35 169 查看
字典树,又称标定搜索树,是一种树形结构,也是一种哈希树的变形,典型的应用包括统计,排序和保存大量的字符串,但又不局限于字符串,还可以是数字等,所以常被搜索引擎系统用来进行词频统计,如搜索网站可用字典树进行热门搜索词的统计。它的特点是:利用字符串的公共前辍来节约存储空间,最大限度的减小字符串的比较,查询效率比哈希表高。对于大量数据而言,字典树所需要的空间相对较大,但是对于查询某个单词而言,其时间复杂度为O(n), n为字符串的长度,对于大量字符串而言,这样的查找速度是相当可观的。

字典树的树形图为:



线性结构为:



其中第一节点为根节点,不存储数据。当查询某个单词时,从根节点开始,向下遍历,如要查找hi,则h->i,时间复杂度为O(2), 如果用常规查找,即从头开始遍历,需要O(15)即所有字符串的总长度,这样的对于大量数据而言,查找效率是不现实的。 如果要查找一个不存在的字符串如kkk, 当遍历发现不存在以k开头的字符串,直接跳出,所以说查询效率是非常高的。

同时可以将大量字符数据保存到字典树中,利用字典树的公共前辍,可以大幅度地节约空间,即在字典树的一个分枝中可以保存大量有相同前辍的字符。

字典树的实现:

next表示每层有多少种类的数,如果为小写字母为26,大小写为52等等

v表示一个字典树有多少公共的前辍,这个可以根据程序需要而改变。

#define MAX 26
typedef struct Trie
{
Trie *next[MAX];
int v;   //根据需要变化
Trie() { //初始化节点的next的各个节点为NULL
for (int i = 0; i < MAX; i++)
next[i] = NULL;
}
};

Trie *root = new Trie;
//创建字典树
void createTrie(char *str)
{
int len = strlen(str);
Trie *p = root, *q;
for(int i=0; i<len; ++i)
{
int id = str[i]-'0';
if(p->next[id] == NULL)
{
q = new Trie;
q->v = 1;    //初始v==1
for(int j=0; j<MAX; ++j)
q->next[j] = NULL;
p->next[id] = q;
p = p->next[id];
}
else
{
p->next[id]->v++;
p = p->next[id];
}
}
p->v = -1;   //若为结尾,则将v改成-1表示
}
//在字典树中查找字符串
int findTrie(char *str)
{
int len = strlen(str);
Trie *p = root;
for(int i=0; i<len; ++i)
{
int id = str[i]-'0';
p = p->next[id];
if(p == NULL)   //若为空集,表示不存以此为前缀的串
return 0;
if(p->v == -1)   //字符集中已有串是此串的前缀
return -1;
}
return -1;   //此串是字符集中某串的前缀
}
//对于上述创建的字典树,有时会超内存,所以要释放内存
int dealTrie(Trie* T)
{
int i;
if(T==NULL)
return 0;
for(i=0;i<MAX;i++)
{
if(T->next[i]!=NULL)
deal(T->next[i]);
}
delete T;
return 0;
}


字典树的查找过程:

(1)每次从根节点开始一次检索;

(2)在取得字符串的第一个关键字后,根据该关键字选择对应的子树并转到该子树继续进行检索。

(3)在子树上找到第二个关键字后,并进行进一步在相应子树上进行检索。

(4)迭代过程......(若遇到找不到的关键字即结束查找,或者按需要创建节点)

(4)在某个节点上,字符串的所有关键字均找到,读取该节点上的附加信息,即完成查找。

相关使用题目:

http://198.74.100.235/wateroj/web/problem.php?id=1001

1001: 土豪我们做朋友吧!

Time Limit: 1 Sec Memory Limit: 16 MB
Submit: 149 Solved: 27
[Submit][Status][Web Board]

Description

Water最近掌握了一大波土豪的电话,他决定一个个打电话去找土豪做朋友。可是土豪们不堪骚扰,于是联合通信公司对Water的电话做了一个封杀规则。
每当Water打出一个电话后,该电话号码以及以该电话号码开始的号码都永久无法被Water打出。
举个栗子,Water打出了号码12345,不管有没人接电话,下一次拨号时12345及12345*(类似1234567)的号码都无法拨出,只能听到“您拨打的用户不接你电话,请不要再拨”。
但是如果Water先拨打了1234567,下一次若为12345还是可以打出的。
Water是个锲而不舍的人,他会把他手头上的电话列表按列表顺序都打一次。

Input

第一行为N [1,1 000 000]。

接下来N行是N个土豪的电话。电话为长度在[1,100]的数字序列。

Output

一个整数M,代表Water成功骚扰到的土豪的个数。(即没有被封杀的电话个数)

Sample Input

5
123
12
12345
123
23451

Sample Output

3

HINT

实现代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int MAX = 10;
struct Node {
int v;
Node* next[MAX];//下一层字典
Node() {
for (int i = 0; i < MAX; i++)
next[i] = NULL;
}
};
Node* root = new Node;
void trieTree (char* str, int& number) {
int len = strlen(str);
bool flag = true;
Node* p = root, *q;//每次都从字典树的根节点开始遍历
for (int i = 0; i < len; i++) {
int location = str[i]-'0';//可以将字符串中的数字转换为int型的数字
if (p->next[location] == NULL) {
q = new Node;
q->v = 1;
p->next[location] = q;
p = p->next[location];
} else {
if (p->next[location]->v == -1) {//如果经过了该已经输入的最短号码的最后一位,则说明该输入的号码不能打通,即令flag=false
flag = false;
p = p->next[location];//这步很关键,因为会关系到35行的赋值,即标示最短号码的最后一位为-1
break;//跳出循环,不将该电话号码存入,节省内存,因为改题只需要判断某个电话号码可不可以打通
}
p = p->next[location];
}
}
p->v = -1;
if (flag == true)//如果未经过最短号码,则说明不是与最短号码有共同前辍的,可以打通
number++;
}
int main() {
int phones;
char* phone;
int number = 0;
phone = new char[101];
scanf("%d", &phones);
for (int i = 0; i < phones; i++) {
scanf("%s", phone);
trieTree(phone, number);
}
printf("%d\n", number);
return 0;
}


相关题目链接:

统计难题:http://acm.hdu.edu.cn/showproblem.php?pid=1251

Phone List: http://acm.hdu.edu.cn/showproblem.php?pid=1671

字典树资料:算法合集之《浅析字母树在信息学竞赛中的应用》

http://www.rayfile.com/zh-cn/files/cb23e411-c735-11df-934c-0015c55db73d/

参考博客:/article/5075800.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐