Trie树学习

最近重看了下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;
}


这个题目需要考虑的比较全面,比如关键字是11,全文中可能多次出现!再比如出现一个关键字是另一个关键字的后缀,比如1314515151和515151
另外这个题目的测试数据全都是可以找到key的!





【资源说明】 1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用!在使用过程中,如有问题或建议,请及时私信沟通。 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 本文介绍了基于QEM(Quadric Error Metrics,二次误差度量)的优化网格简化算法的C和C++实现源码及其相关文档。这一算法主要应用于计算机图形学领域,用于优化三维模型的多边形数量,使之在保持原有模型特征的前提下实现简化。简化的目的是为了提高渲染速度,减少计算资源消耗,以及便于网络传输等。 本项目的核心是网格简化算法的实现,而QEM作为该算法的核心,是一种衡量简化误差的数学方法。通过计算每个顶点的二次误差矩阵来评估简化操作的误差,并以此来指导网格简化过程。QEM算法因其高效性和准确性在计算机图形学中广泛应用,尤其在实时渲染和三维打印领域。 项目代码包含C和C++两种语言版本,这意味着它可以在多种开发环境中运行,增加了其适用范围。对于计算机相关专业的学生、教师和行业从业者来说,这个项目提供了丰富的学习和实践机会。无论是作为学习编程的入门材料,还是作为深入研究计算机图形学的项目,该项目都具有实用价值。 此外,项目包含的论文文档为理解网格简化算法提供了理论基础。论文详细介绍了QEM算法的原理、实施步骤以及与其他算法的对比分析。这不仅有助于加深对算法的理解,也为那些希望将算法应用于自己研究领域的人员提供了参考资料。 资源说明文档强调了项目的稳定性和可靠性,并鼓励用户在使用过程中提出问题或建议,以便不断地优化和完善项目。文档还提醒用户注意查看,以获取使用该项目的所有必要信息。 项目的文件名称列表中包含了加水印的论文文档、资源说明文件和实际的项目代码目录,后者位于名为Mesh-Simplification-master的目录下。用户可以将这些资源用于多种教学和研究目的,包括课程设计、毕业设计、项目立项演示等。 这个项目是一个宝贵的资源,它不仅提供了一个成熟的技术实现,而且为进一步的研究和学习提供了坚实的基础。它鼓励用户探索和扩展,以期在计算机图形学领域中取得更深入的研究成果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值