浅析字典树

简介

字典树是一种利用树形结构来存储字符串的数据结构。利用树形结构一对多的特点,每个元素都可以有多个子元素,因此在字符串比较过程中,只需要判断最后一个元素的祖先链是否相等,时间复杂度为 O ( n ) O\left ( n \right) O(n)
先来看一个字典树的实例图:由于本人太懒,引用图片来源于OI Wiki 字典树 (Trie) (这个图画起来是真的费时间啊!!!)

通过对不同的字符串,可以根据其共有的前缀部分来建立一棵树,例如上图中125就代表字符串    a a    \;aa\; aa

不难得到实现代码如下

int trie[maxn][26];		//列的个数取决于字符串中字符的种类,这里只考虑小写字母
int e[maxn];
int num;
void insert(char* str)		//插入字符串
{
	int pos = 0;			//初始化根的位置
	for (int i = 0; str[i]; i++)
	{
		int leaf = str[i] - 'a';
		if (!trie[pos][leaf])
		{
			trie[pos][leaf] = ++num;		//还没有编号的进行编号
		}
		pos = trie[pos][leaf];				//迭代更新位置
	}
	e[pos]++;
}
bool find(char* str)
{
	int pos = root;
	for (int i = 0; str[i]; i++)
	{
		int leaf = str[i] - 'a';
		if (trie[pos][leaf] == 0)		//如果该结点标号为0,则一定不存在该字符串
			return false;
		pos = trie[pos][leaf];		//继续向下查询
	}
	if (!e[pos])
	{
		return false;
	}
	return true;
}

例题:

Codeforces 1285D

题目大意是给出    n    \;n\; n个数,要求找到一个数    X    \;X\; X,使得所有    a i ⊕ X    \;a_i⊕X\; aiX 中最大的值最小,输出这个异或值。
这是一道经典的    01 T r i e    \;01Trie\; 01Trie,如何来考虑这个问题首先我们最所有的数建立起一个01字典树,也就是将一个数转换为二进制后插入到一个字典树中,接下来来考虑这样一个问题,由于只有0和1两个元素,所以构成的字典树一定是一颗二叉树,那么对应的字典树上每个结点也就只可能包含1或2个结点(非叶结点)。如果只有一个结点,为了要让异或值尽可能的小,所以这一位上的结果一定是为0的,因为只有一个结点,不管是0还是1,只要选和它相同的数就能使异或值为0;而对于两个结点,在这一位上的异或值一定就是1,这是因为两个结点一定是0和1各一个,所以无论这一位选谁异或值一定都是1,然后就需要动态规划去考虑两个结点哪个字数的异或值更小,可以递归实现这个过程。于是解决方法如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 5;
int trie[maxn][2];
int n, cnt;
inline int read()			//一个无聊的快读过程
{
	int t = 0;
	char ch = getchar();
	while (ch < '0' || ch>'9')
	{
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		t = (t << 3) + (t << 1) + (ch ^ 48);
		ch = getchar();
	}
	return t;
}
inline void write(int t)		//一个无聊的快写过程
{
	if (t > 9)
	{
		write(t / 10);
	}
	putchar(t % 10 + '0');
}
inline void insert(int t)			//一个无聊的插入过程
{
	int pos = 0;
	for (int i = 29; i >= 0; i--)		//题目中最大取到2^30,所以从高位最大值出依此插入每一位
	{
		int x = (t >> i) & 1;
		if (!trie[pos][x])
		{
			trie[pos][x] = ++cnt;
		}
		pos = trie[pos][x];
	}
}
inline int solve(int t, int cur)		//一个重点的解决过程
{
	if (t == -1)			//因为建树的30位是从0-29,所以递归边界要到-1
	{
		return 0;
	}
	if (!trie[cur][0])			//两种对应的一个结点的情况
	{
		return solve(t - 1, trie[cur][1]);
	}
	else if (!trie[cur][1])
	{
		return solve(t - 1, trie[cur][0]);
	}
	else
	{
		//这里是关键部分,如果存在两个结点,首先当前位一定是1,然后再在两个子树中选取较小的一部分
		return (1 << t) + min(solve(t - 1, trie[cur][0]), solve(t - 1, trie[cur][1]));
	}
}
int main()			//一个无聊的主函数
{
	n = read();
	while (n--)
	{
		insert(read());
	}
	write(solve(29, 0));
	putchar(10);
	return 0;
}

POJ 1204

题目大意为给出一个 L × C L×C L×C大小的字符矩阵,然后给出多个模式串,要求在矩阵中找出能够匹配每个模式串的首字母的位置,并且字符串的摆放方式有8种,找到匹配的字符串后还要给出字符串的朝向。

分析题目,对于多模式串肯定是要建立字典树来查询,所以可以将所有模式串建立字典树,然后枚举矩阵中所有字符的每个方向,匹配到返回坐标信息和方向。

Solution:

#include<iostream>
using namespace std;
#include<cstdio>
#include<cstdlib>
#include<cstring>
const int maxn = 10010;
int dir[][2] = { -1,0,-1,1,0,1,1,1,1,0,1,-1,0,-1,-1,-1 };		//用于表示八个方向
char s[1005][1005];			//表示字符矩阵
char str[1005];			//待查询字符串,即模式串
int n, m, T;
int cnt;
struct trie
{
	int c[30];
	int s;		//标记是第几个模式串
}t[maxn * 50];
struct answer		//用于记录答案信息,即坐标和方向
{
	int x, y, z;
}ans[1005];
void insert(int num)
{
	int x = 0;
	int len = strlen(str);
	for (int i = 0; i < len; i++)
	{
		int y = str[i] - 'A';
		if (!t[x].c[y])
		{
			cnt++;
			t[x].c[y] = cnt;
		}
		x = t[x].c[y];
	}
	t[x].s = num;
	return;
}
bool check(int x, int y)		//判断是否还在矩阵内部
{
	if (x >= 0 && x < n && y >= 0 && y < m)
		return true;
	return false;
}
void query(int x, int y, int z)
{
	int pos_x = x, pos_y = y;
	int cur = 0;
	while (check(x, y))
	{
		int b = (s[x][y] - 'A');
		if (!t[cur].c[b])
			return;
		cur = t[cur].c[b];
		if (t[cur].s)
		{
			int num = t[cur].s;
			ans[num].x = pos_x;
			ans[num].y = pos_y;
			ans[num].z = z;
		}
		x = x + dir[z][0];
		y = y + dir[z][1];
	}
	return;
}
void solve()
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			for (int k = 0; k < 8; k++)
			{
				query(i, j, k);			//枚举查询每一个字符的八个方向构成的字符串是否符合要求
			}
		}
	}

}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m >> T;
	for (int i = 0; i < n; i++)
	{
		cin >> s[i];
	}
	for (int i = 1; i <= T; i++)
	{
		cin >> str;
		insert(i);
	}
	solve();
	for (int i = 1; i <= T; i++)
	{
		cout << ans[i].x << ' ' << ans[i].y << ' ' << (char)(ans[i].z + 'A') << endl;
	}		
	return 0;
}

完结撒花!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值