【模板】数据结构之字典树,并查集,堆,散列表

字典树(Trie树)

又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

它有3个基本性质:

根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。

高效地存储和查找字符串集合的数据结构,降低存储和查询字符串的复杂度

int son[N][26], cnt[N], idx;
//0号点既是根结点,又是空结点 
//son[][]存储树中每个结点的子节点
//cnt[]存储以每个结点结尾的单词数量
void insert(char *str)
{
	int p = 0;//父节点
	for (int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';//字符映射数字
		if (!son[p][u])son[p][u] = ++idx;//新增该字符作为子结点,存储下标
			p = son[p][u];
	}
	cnt[p]++;
}
int query(char *str)
{
	int p = 0;
	for (int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';
		if (!son[p][u])return 0;//无此字符串
		p = son[p][u];
	}
	return cnt[p];//字符串出现次数,可能为0
}

练习

2156 - Trie字符串统计

#3266. 最大异或对

并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。

并查集支持的两个操作:

1.将两个集合合并

2.询问两个元素是否在一个集合当中(近乎O(1))

基本原理:每个集合用一棵树来表示。树根的编号就是整个集合的编号。对于每个结点都存储它的父节点,p[x]代表x的父节点。

判断树根:if(p[x]==x)

求x的集合编号(根节点):while(p[x]!=x)x=p[x];

合并两个集合:p[x]是x的集合编号,p[y]是y的集合编号。p[x]=y;

1)朴素并查集:

int p[N];//存储每个点的父结点(根节点值即代表集合编号)
int find(int x)//返回x的根结点
{
	if (p[x] != x)p[x] = find(p[x]);//路径压缩优化
	return p[x];//如果没有到达根节点,则一直向上查找,否则返回根节点
}
//初始化,假定结点编号是1~n
for (int i = 1; i <= n; i++)p[i] = i;//初始根节点是自身
//合并a和b所在的两个集合:
p[find(a)] = find(b);

(2)维护size的并查集:

int p[N], size[N];//size[]只有根节点的有意义
int find(int x)
{
	if (p[x] != x)p[x] = find(p[x]);
	return p[x];
}
for (int i = 1; i <= n; i++)//初始化
{
	p[i] = i;
	size[i] = 1;
}
size[find(b)] += size[find(a)];//合并集合大小
p[find(a)] = find(b);

(3)维护到根节点距离的并查集:

int p[N], d[N];//d[x]存储x到p[x]的距离
int find(int x)
{
	if (p[x] != x)
	{
		int u = find(p[x]);
		d[x] += d[p[x]];
		p[x] = u;
	}
	return p[x];
}
for (int i = 1; i <= n; i++)
{
	p[i] = i;
	d[i] = 0;
}
p[find(a)] = find(b);
d[find(a)] = distance;//根据具体问题,初始化find(a)的偏移量

练习

2157 - 合并集合(只能过测试点1)

2105 - 连通块中点的数量​​​​​​

​​​​​​[NOI2001]食物链

P1551 亲戚 

相关资料

算法学习笔记(1) : 并查集

堆(优先队列)

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

  • 堆总是一棵完全二叉树。

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆支持的操作:

1.插入一个数 heap[++size]=x;up(size);

2.求集合当中的最小值 heap[1];//1+2操作复杂度为O(n^2),利用堆优化为O(logn)

3.删除最小值 heap[1]=heap[size--],down(1);//可用来排序,最差时间复杂度优于快排

4.删除任意一个元素 heap[k]=heap[size--];up(k),down(k);//up(),down()只会执行一个

5.修改任意一个元素 heap[k]=k;up(k),down(k);

关于建堆:如果所有子树都为最小堆,整棵数为最小堆(递归定义),所以从最后一个结点开始,依次判断以此结点为根的子树是否符合最小堆特性。因为叶节点没有儿子,所以所有以叶节点为根节点的子树都符合最小堆的特性,即父结点的值比子结点的值小,所以无需处理,直接从第n/2个结点开始处理(最后一个非叶节点是n/2)。可证时间复杂度为O(n)。

//h[N]存储堆中的值,h[1]是堆顶,x的左儿子是2x,右儿子是2x+1
//ph[k]存储第k个插入的点在堆中的位置
//hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
//交换两个点,及其映射关系
void heap_swap(int a, int b)
{
	swap(ph[hp[a]], ph[hp[b]]);
	swap(hp[a], hp[b]);
	swap(h[a], h[b]);
}
void down(int u)
{
	int t = u;
	if (u * 2 <= size&h[u * 2] < h[t])t = u * 2;//如果小于左儿子
	if (u * 2 + 1 <= size&h[u * 2 + 1] < h[t])t = u * 2 + 1;//如果小于右儿子
	if (u != t)//最小结点不是自己
	{
		heap_swap(u, t);
		down(t);//下降到堆底
	}
}
void up(int u)
{
	while (u / 2 && h[u] < h[u / 2])//左右儿子(2*u,2*u+1)父结点都为u/2,因为'/'向下取整
	{
		heap_swap(u, u / 2);
		u >>= 1;
	}
}
for (int i = n / 2; i; i--)down(i);//O(n)建堆

练习

堆排序

​​​​​​2159 - 模拟堆

哈希表(散列表)

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

将大数据结合映射为小数据集合,可以在数据范围很大的情况下用来判重

1.一般哈希

1)拉链法
int h[N], e[N], ne[N], idx;//h[]存储哈希值,e[]只需创建一个,N一般取质数,且距离2^n尽可能远
//向哈希表中插入一个数
void insert(int x)//头插法
{
	int k = (x%N + N) % N;//x可能为负,所以+N%N映射到正数域
	e[idx] = x;
	ne[idx] = h[k];
	h[k] = idx++;
}
//在哈希表中查询某个数是否存在
int find(int x)
{
	int k = (x%N + N) % N;
	for (int i = h[k]; i != -1; i = ne[k])//遍历h[k]链
	if (e[i] == x)
		return 1;
	return 0;
}

(2)开放寻址法
int h[N];
const int null = 0x3f3f3f3f;//哈希值可能为负数,所以以足够大的数为空
//如果x在哈希表中,返回x的下标:如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
	int t = (x%N + N) % N;
	while (h[t] != null&&h[t]!=x)//当前位置已有值且不是x
	{
		t++;
		if (t == N)t = 0;
	}
	return t;
}

练习

2104 - 模拟散列表

2.字符串哈希

核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低

小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果

typedef unsigned long long ULL;
UUL h[N], p[N];//h[k]存储字符串前k个字母的哈希值,p[k]存储P^k mod 2^64
//初始化
p[0] = 1;
for (int i = 1; i <= n; i++)
{
	h[i] = h[i - 1] * p + str[i];
	p[i] = p[i - 1] * p;
}
//计算子串str[l~r]的哈希值
ULL get(int l, int r)
{
	return h[r] - h[l - 1] * p[r - l + 1];
}

P3370 【模板】字符串哈希

模板参考acwingP1551 亲戚

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值