首页 > 其他分享 >数据结构 —— Trie 树

数据结构 —— Trie 树

时间:2024-07-01 18:23:42浏览次数:15  
标签:include Trie tr int ans 字符串 数据结构

一个笔记需要一张头图:

Trie 树是一种维护(广义)字符串(我们认为广义字符串是一切可以被线性描述的类型,例如,我们认为整数(无论是哪种进制下)是一种广义字符串;有理数也是一种广义字符串(使用无限循环小数方式表述,可能需要一些特殊处理。))的数据结构,其特征为适于处理前缀类型或寻找类型(i.e. 以遍历每一位的方式找到一个符合的广义字符串)的问题。

下文我们使用⌈字符串⌋来表示广义字符串,特别的,也表示(在上下文中推断出的)正在使用 Trie 树维护的类型。

一个典型的 Trie 树如下图所示:(图源 OI-wiki)

trie1

从图中可以看出一部分 Trie 树的写法:多半需要一个数组来维护节点及其儿子节点,也就是说,一棵很普通的树的写法。

那么容易写出结构体定义:

struct Trie {
	int tr[100010][20], end[100010], root, cnt;
	Trie() {root = 1;cnt = 1;memset(tr, 0, sizeof(tr));memset(end, 0, sizeof(end));}
};

可以看到在这里我们定义了end数组,其意义为在该节点上结束的字符串数量。

插入是容易实现的(这里认为维护的是数字串):

void insert(string s) {
    int u = root;
    for (int i = 0; i <= s.size() - 1; i++) {
        if (!tr[u][s[i] - '0']) {
            tr[u][s[i] - '0'] = ++cnt;
        }
        u = tr[u][s[i] - '0'];
    }
    end[u]++;
}

查询函数通常与题目非常相关,但是我们可以提炼出一个大致的模型(这里和上面做出相同的假设,i.e.,维护的是数字串):

type query(string s) {
    int u = root;
    type ans;
    rep (i, 0, s.size() - 1) {
        u = tr[u][s[i] - '0'];
        doSomethingWithAns(ans, u);
    }
    return ans;
}

顺理成章的,接下来是一些例题…… 不过我们需要将问题分类。

前缀类问题

前缀类问题,顾名思义,即查询有关于某个字符串前缀的问题。

注意到 Trie 树非常适合做这件事,原因是 Trie 树的结构使得查询某个字符串的前缀时会经过所有维护其前缀的节点(这些节点在插入这个字符串时被创建)。

给出一道非常经典的例题。

Phone List

一句话题意:给定 \(n\) 个数字串,询问是否存在两个字符串 \(a, b\) 使得 \(a\) 是 \(b\) 的前缀。

对这\(n\)个串建立 Trie 即可。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#ifndef ONLINE_JUDGE
#include "../../../debug.hpp"
#else
#define debug(...)
#define debugArr(...)
#endif
template <typename T>
using V = vector<T>;
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define rept(type, i, x, y) for (type i = x; i <= y; i++)
#define all(v) v.begin(), v.end()
#define range(v) 0, v.size() - 1 // 0 - indexed
#define sz(v) v.size()
using ll = long long;

struct Trie {
	int tr[100010][20], end[100010], root, cnt;
	Trie() {root = 1;cnt = 1;memset(tr, 0, sizeof(tr));memset(end, 0, sizeof(end));}
	void insert(string s) {
		int u = root;
		rep (i, 0, s.size() - 1) {
			if (!tr[u][s[i] - '0']) {
				tr[u][s[i] - '0'] = ++cnt;
			}
			u = tr[u][s[i] - '0'];
		}
		debug(s, u);
		end[u]++;
	}
	bool query(string s) {
		int u = root;
		bool flag = false;
		rep (i, 0, s.size() - 1) {
			u = tr[u][s[i] - '0'];
		}
		debug(s, u);
		for (int i = 0; i < 10; i++) {
			if (tr[u][i]) flag = true;
		}
		return (flag || end[u] > 1);
	}
};

int t, n;
string s[10010];

int main()
{
	// cout << "is running" << "\n";
	cin >> t;
	while (t--) {
		cin >> n;
		rep (i, 1, n) {
			cin >> s[i];
		}
		Trie tree;
		rep (i, 1, n) {
			tree.insert(s[i]);
		}
		bool ans = false;
		rep (i, 1, n) {
			ans |= tree.query(s[i]);
			debug(ans);
		}
		cout << (ans ? "NO" : "YES") << "\n";
	}
}

寻找类

这一类问题通常的需求是寻找一个符合某种性质的字符串。

同样给出一个典型例题。

The XOR Largest Pair

一句话题意:从\(a_1,a_2,\dots,a_n\)中寻找两个数使其异或值最大。

显然先枚举一个,随后需要处理⌈异或值最大⌋这个问题。

把数字放在二进制下考虑是合理的,随后发现一个简单的性质:如果 \(a_i > b_i\) 且 \(a_j \geq b_j\) 对所有的 \(j\) 成立(此处 \(a,b\) 为正整数,\(a_i\)代表二进制下 \(a\) 的最高第 \(i\) 位),那么有 \(a > b\)。

回到这道题,我们发现对于 \(a_i\) 如果可以选出一个数 \(a_j\),使得 \(a_i \oplus a_j\) 的最高位为 \(1\),那 \(a_j\) 一定比使异或值最高位为 \(0\) 的数优。这使我们思考按位由高到低贪心。恰好,Trie 树适于处理这种情况。

接下来的实现是简单的,给出代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#ifndef ONLINE_JUDGE
#include "../../../debug.hpp"
#else
#define debug(...)
#define debugArr(...)
#endif
template <typename T>
using V = vector<T>;
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define per(i, x, y) for (int i = x; i >= y; i--)
#define rept(type, i, x, y) for (type i = x; i <= y; i++)
#define all(v) v.begin(), v.end()
#define range(v) 0, v.size() - 1 // 0 - indexed
#define sz(v) v.size()
using ll = long long;
int tr[3000010][20], root = 1, cnt = 1;
void insert(int s) {
	int u = root;
	per (i, 32, 1) {
		int v = (s & (1 << (i - 1))) >> (i - 1);
		if (!tr[u][v]) {
			tr[u][v] = ++cnt;
		}
		debug(s, u, tr[u][v]);
		u = tr[u][v];
	}
	debug(s, u);
}
int query(int s) {
	int u = root, ans = 0;
	per (i, 32, 1) {
		int v = ((s & (1 << (i - 1))) >> (i - 1));
		debug(s, v, u);
		if (tr[u][v ^ 1]) {
			u = tr[u][v ^ 1];
			ans <<= 1;
			ans += v ^ 1;
		} else {
			u = tr[u][v];
			ans <<= 1;
			ans += v;
		}
	}
	debug(s, ans);
	return ans;
}

int a[100010], n;

int main()
{
	cin >> n;
	rep (i, 1, n) {
		cin >> a[i];
		insert(a[i]);
	}
	int ans = 0;
	rep (i, 1, n) {
		int res = query(a[i]);
		ans = max(ans, res ^ a[i]);
	}
	cout << ans << "\n";
}

标签:include,Trie,tr,int,ans,字符串,数据结构
From: https://www.cnblogs.com/IANYEYZ/p/18278595

相关文章

  • MySQL Public Key Retrieval is not allowed 解决指南
    MySQLPublicKeyRetrievalisnotallowed解决指南基本概念与作用说明完整代码示例与解决方案示例一:检查用户权限示例二:检查KMS配置示例三:检查加密列定义示例四:重置密钥功能使用思路与最佳实践实际工作开发技巧在现代数据库管理中,加密和密钥管理是保障数据安全......
  • 探索数据结构:队列的的实现与应用
     ......
  • 堆数据结构
    堆(Heap)是一种特殊的树形数据结构,通常被实现为一个完全二叉树,以数组的形式存储。堆主要用于实现优先队列,它有两种基本形式:最大堆(MaxHeap)和最小堆(MinHeap)。特点完全二叉树:堆在逻辑上是一个完全二叉树,这意味着除了最后一层外,每一层的节点都是满的,并且最后一层的节点都靠左排列。......
  • 【408考点之数据结构】排序的基本概念
    排序的基本概念排序是计算机科学中的一个基本操作,目的是将一组无序的数据元素按照特定的顺序排列起来。排序在数据管理、检索和分析中有着广泛的应用,能够提高数据处理的效率和准确性。1.排序的定义排序(Sorting)是指将一组记录按某个关键字或多个关键字的大小关系进行排列......
  • 【408考点之数据结构】顺序查找和折半查找
    顺序查找和折半查找在数据处理中,查找操作是非常重要的一部分。顺序查找和折半查找是两种常见的查找方法,它们各有优缺点和适用场景。以下是对这两种查找方法的详细介绍。1.顺序查找定义:顺序查找(SequentialSearch),也称线性查找,是一种最简单、最直接的查找方法。它从数据集......
  • [JLU] 数据结构与算法上机题解思路分享-第一次上机
    前言首先,请务必自己尽全力尝试实现题目,直接看成品代码,思维就被拘束了,也很容易被查重。这里只是思路解析的博客,代码仓库是JLU_Data_Structures_Record希望你能在这里找到你想要的:)正文A调皮的哈利分数30作者朱允刚单位吉林大学贝蒂是个打字高手,打字时有不看屏幕的习......
  • Mysql--B+树--数据结构
    基本概念-B+树/B树B树(B-tree)和B+树(B+tree)是常见的自平衡搜索树数据结构,用于在存储和检索大量数据时提供高效的操作。它们具有一些共同的基本概念:节点(Node):B树和B+树的数据存储在节点中。节点可以包含多个关键字和对应的指针。在B树中,叶子节点和内部节点的结构相同,都存储数据......
  • 【数据结构与算法】拓扑排序,关键活动,关键路径 详解
    拓扑排序算法booltopologicalSort(){ stack<int>stk; intid[N]; intcnt=0; for(inti=1;i<=n;i++){ if(!inDeg[i]){ stk.push(i); } id[i]=inDeg[i]; } while(stk.size()){ intt=stk.top(); stk.pop(); cout<<t<......
  • 【数据结构与算法】最小生成树,Prim算法,Kruskal算法 详解
    最小生成树的实际应用背景。最节省经费的前提下,在n个城市之间建立通信联络网。Kruskal算法(基于并查集)voidinit(){for(inti=1;i<=n;i++){pre[i]=i;}}llroot(lla){lli=a;while(pre[i]!=i){i=pre[i];......
  • 认识Retrieval Augmented Generation(RAG)
    什么是RAG?Retrieval-AugmentedGeneration(RAG)是一种结合信息检索和生成式AI技术的框架。它通过从外部数据源检索信息,增强语言模型(如GPT-3)的生成能力,从而提供更加准确和相关的回答。RAG的组成部分信息检索模块(Retriever)功能:从预先构建的知识库或文档库中检索与用......