首页 > 其他分享 >字典树(trie)

字典树(trie)

时间:2024-01-19 15:00:35浏览次数:18  
标签:cur trie int pass 字符串 root 节点 字典

字典树是一种树形结构,节点存储的不是完整的字符串而是前缀字符。

字典树的优点:因为通过前缀字符来选择树上的分支,所以可以节省时间

字典树的缺点:比较浪费空间,与总字符数量有关

字典树的根节点不存储字符。

用动态分配空间实现(常见)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

typedef struct TreeNode {
    int pass;//记录有多少个字符串以当前字符串做前缀 
    int end;//计入有多少个字符串以当前字符串结束 
    struct TreeNode* next[26];//指向下一个字符,即下一个节点 
} Node;

typedef struct trie {//只需要知道根节点就可以访问整个字典树 
	Node* root;
} Trie;

void deleteNode(Node* root)//从root层序遍历字典树,删除root的整颗树,包括 root节点 
{
	if (root == NULL) {
		return ;
	}
	
    Node* q[100000] = { 0 };//准备一个队列 
    int l = 0;//队列头 
    int r = 0;//队列尾 

    q[r++] = root;//根节点入队 

    while (l != r) {//只要队列中还有节点 
        Node* t = q[l++];//出队 

        for (int i = 0; i < 26; i++) {//访问所有孩子节点 
            if (t->next[i]) {//只要有孩子 
                q[r++] = t->next[i];//把孩子节点入队 
            }
        }
        free(t);//释放刚刚出队的节点,因为已经处理完了它的孩子节点,所以可以释放它了  
    }      
}

void freeTrie(Trie* root) {
	deleteNode(root -> root);//先释放字典树 
	free(root);//再释放root 
}

Node* makeNode(void) {//创建一个节点 
    Node* node = (Node*)malloc(sizeof(Node));//malloc一个节点 

    node->end = 0;//初始化 
    node->pass = 0;//初始化 
    for (int i = 0; i < 26; i++) {
        node->next[i] = NULL;//指向空代表没有路了 
    }

    return node;//返回创建的节点 
}

void insert(Trie* root, char s[]) {//把s放入字典树中 
    Node* r = root->root;//指向字典树的根节点 
    
    r->pass++;//先让根节点的pass加一,因为来到了根节点 

    for (int i = 0; s[i]; i++) {//遍历s中的每一个字符 
        int index = s[i] - 'a';//取出对应的下标,0 -> 'a' ~ 25 -> 'z' 

        if (r->next[index] == NULL) {//如果没有路了,那么就创建一个节点 
                r->next[index] = makeNode();//让对应的下标指向新创建的节点 
        }
        r = r->next[index];//来到下一个节点 
        r->pass++;//因为来到一个新的节点,所以pass++ 
    }

    r->end++;//遍历完了之后,r来到最后一个节点,然后end++,表示以字符串s结尾的字符串的个数加一 
}

int search(Trie* root, char s[]) {//返回在字典树中字符串s出现的次数 
    Node* cur = root->root;//指向字典树的根节点 

    for (int i = 0; s[i]; i++) {//遍历字符串中的每一个字符 
        int index = s[i] - 'a';//计算字符所对应的下标 

        if (cur->next[index] == NULL)//如果没有路了代表肯定没有这个字符串 
        {
                return 0;//返回0,表示出现0次 
        }
        cur = cur->next[index];//去下一个节点 
    }

    return cur -> end;//最后返回最后一个节点的end,因为end表示以当前路径结尾组成的字符串有多少个 
}

void deleteWord(Trie* root, char s[]) {//删除字典树中,字符串s,只删除一次 
    if (search(root, s)) {//只有存在字典树中才删除 
        Node* cur = root->root;//指向字典树的根节点 
        --cur->pass;//来到一个节点就让pass减一 

        for (int i = 0; s[i]; i++) {//遍历s中的每一个字符串 
            int index = s[i] - 'a';//取出对应的下标 
			//注意是下一个节点的 pass,因为要删掉的是下一个节点而不是当前的节点 
            if (--cur -> next[index] -> pass <= 0) {//如果下一个节点的pass减一之后 <= 0,那么释放下一个节点,因为后面的节点都没有用了。 
                    deleteNode(cur->next[index]);//删除下一个节点的整颗树 
                    cur->next[index] = NULL;//让它指向空 
                    return;//没有必要再删了,因为已经删除完了,所以结束函数 
            }
            cur = cur->next[index];//去对应的下一个节点 
        }

        cur->end--;//让最后一个节点的end减一,表示字符串s在字典树中的出现次数减一 
    }
}

int prefixNumber(Trie* root, char s[]) {//返回在字典树中有多少个字符串以字符串s做前缀 
    Node* cur = root->root;//指向字典树的根节点 

    for (int i = 0; s[i]; i++) {//遍历字符串s的每一个字符 
        int index = s[i] - 'a';//取出对应的下标 

        if (cur->next[index] == NULL) {//如果没有路了代表字符串s没有在字典树中,那么就不可能有以s做前缀的字符串,所以返回0 
                return 0;
        }

        cur = cur->next[index];//去对应的下一个节点 
    }

    return cur->pass;//pass含义就是以路径上的字符串做前缀的字符串个数 
}

int main() {
    int a;
    int n;
    char s[21] = "";
    Trie* r = (Trie*)malloc(sizeof(Trie));

    r->root = (Node*)malloc(sizeof(Node));

    r->root->end = 0;
    r->root->pass = 0;
    for (int i = 0; i < 26; i++) {
        r->root->next[i] = NULL;
    }

    scanf("%d", &n);

    while (n--) {
        scanf("%d %s", &a, &s);

        if (a == 1) {
                insert(r, s);
        }
        else if (a == 2) {
                deleteWord(r, s);
        }
        else if (a == 3) {
                if (search(r, s) > 0) {
                        printf("YES\n");
                }
                else {
                        printf("NO\n");
                }
        }
        else {
                int ans = prefixNumber(r, s);

                printf("%d\n", ans);
        }
    }
    
    freeTrie(r);
    
    return 0;
}

如果总字符类型很多的话,可以用哈希表做next数组来节约空间。

静态数组实现

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 150001

int tree[N][26] = {0};//N表示字典树最多同时有n个节点,根节点的下标是1 
int end[N] = {0};//end数组表示以对应字符串的结尾的字符串的个数 
int pass[N] = {0};//pass数组表示以对应字符串做前缀的字符串的个数 
int cnt = 1;//表示使用到了多少下标,很关键的一个变量 

void clear(void) {//从下标1到cnt的空间全部置为0 
	for (int i = 1; i <= cnt; i++) {
		for (int j = 0; j < 26; j++) {
			tree[i][j] = 0;
		}
		pass[i] = 0;
		end[i] = 0;
	}
}

void build(void) {//创建一个字典树就是让cnt等于1 
	cnt = 1;
}

void insert(char s[]) {//插入一个字符串s到字典树中 
	int cur = 1;//指向根节点的下标 
	
	pass[cur]++;//让根节点对应的pass++ 
	
	for (int i = 0; s[i]; i++) {//遍历字符串s中的每一个字符 
		int index = s[i] - 'a';//取出对应的位置 
		
		if (!tree[cur][index]) {//如果tree[cur][index]的值为0,代表没有下一个节点,所以把++cnt的位置给它作为下一个节点 
			tree[cur][index] = ++cnt;
		}
		cur = tree[cur][index];//去下一个节点 
		pass[cur]++;//让对应的pass++ 
	}
	
	end[cur]++;//让对应的end++ 
}

int search(char s[]) {//返回字符串s在字典树中出现了多少次 
	int cur = 1;//来到根节点 
	
	for (int i = 0; s[i]; i++) {
		int index = s[i] - 'a';
		
		if (!tree[cur][index]) {//如果tree[cur][index]的值为0,代表没有路了,也就是在字典树中没有字符串s,所以返回0 
			return 0;
		}
		cur = tree[cur][index];//去下一个节点 
	}
	
	return end[cur];//返回对应end,即字符串s在字典树中出现的次数 
}

void deleteWord(char s[]) {//在字典树中删除字符串s 
	if (search(s) > 0) {//字符串s在字典树中出现过才删除 
		int cur = 1;//来到根节点 

		pass[cur]--;//根节点的pass值减一 
		
		for (int i = 0; s[i]; i++) {
			int index = s[i] - 'a';
			
			if (--pass[tree[cur][index]] <= 0) {//如果下一个节点对应的pass值减一后小于等于0了,那么代表这条分支没有用了,让对应的下标置为0 
				tree[cur][index] = 0;//为什么呢,因为cnt一直在加,没有减过,所以实际上是拿没有用过的空间来用,就像数组实现队列一样,队列尾一直往后面走。 
				return ;
			}
			
			cur = tree[cur][index];//去下一个节点 
		}
		end[cur]--;//对应的end减一 
	}
}

int prefixNumber(char s[]) {//返回以字典树中以字符串s做前缀有多少个字符串 
	int cur = 1;
	
	for (int i = 0; s[i]; i++) {
		int index = s[i] - 'a';
		
		if (!tree[cur][index]) {//如果tree[cur][index]的值为0,代表没有路了,也就是在字典树中没有字符串s 
			return 0;//所以返回0 
		}
		
		cur = tree[cur][index];//去下一个节点 
	}
	
	return pass[cur];//返回对应的pass值 
}

int main(int argc, char* argv[])
{
	int n;
	char s[21] = "";
	int t;
	
	build();
	
	sc("%d", &n);
	
	while(n--) {
		sc("%d %s", &t, s);
		
		if (t == 1) {
			insert(s);
		} 
		else if (t == 2) {
			deleteWord(s);
		}
		else if (t == 3) {
			if (search(s) > 0) {
				pr("YES\n");
			}
			else {
				pr("NO\n");
			}
		}
		else {
			pr("%d\n", prefixNumber(s));
		}
	}
	
	clear();
	
	return 0;
}

静态数组的实现本质是一种模拟链表,这样可以节约空间,因为实现了空间的复用,而且常数时间更快,所以写题目更推荐使用静态数组的实现。

标签:cur,trie,int,pass,字符串,root,节点,字典
From: https://www.cnblogs.com/lwj1239/p/17972273

相关文章

  • P6105 [Ynoi2010] y-fast trie
    更好的阅读体验P6105[Ynoi2010]y-fasttrie首先把所有数对\(C\)取模,分类讨论:\(x+y\geqC\)因为只会取模一次,这时显然取最大值和次大值。\(x+y<C\)一开始的想法是对于每一个数\(a\)找到另一个数满足\(a+b<C\)的最大的\(b\),这样是一棵外向树(环长一定\(=2\)),修改......
  • day08-字典
    字典(Dict)是一种可变、无序的数据类型;那等等...我们回忆一下,字符串列表元祖是什么样的?字符串不可变,有序列表可变,有序元祖不可变,有序如何判断有序和无序呢,我首先确定在字符串、列表、元祖篇我们都讲到了切片取值,说明他们都是有顺序的,而字典是无序的,说明字典无法通过切片取值,......
  • [探讨] 字典管理
    一、问题背景字典来源众多后端代码(枚举,if判断,数据库表字段)前端(判断)系统管理>字典管理二、问题更新不及时(可能后端、前端改了,文档没改)没有通知到相应人员三、讨论3.1需不需要统一1)统一的好处/坏处有哪些坏处:集成的应用过多(有历史系统,很难或根本不可能合并)好处:字......
  • 浅谈 Trie 树
    浅谈Trie树什么是Trie树?Trie树,又称字典树,可用于存储单词。Trie树的根节点不表示任何字母,但是除了根节点的所有字母都表示一个字母。任何一个单词,都可以用一条从根节点出发的路径表示。在路径的终点做一个“结束”标记,对应一个单词的结尾。举个例子:要存储work,word,wo......
  • 若依前后端分离版关联字典值查询数据工具类使用
    场景若依管理系统导出Excel时添加没有的列和关联码表显示中文进行导出:若依管理系统导出Excel时添加没有的列和关联码表显示中文进行导出_若依的导出添加额外的字段信息上面通过关联表的方式实现查询字典值,若依本身提供了查询redis中缓存的字典值的相关方法。可不修改sql的方式去调......
  • (△△△)给定 n 个字符串,请对 n 个字符串按照字典序排列。 数据范围: 1 \le n \le 100
    描述给定n个字符串,请对n个字符串按照字典序排列。数据范围:1\len\le1000\1≤n≤1000,字符串长度满足1\lelen\le100\1≤len≤100输入描述:输入第一行为一个正整数n(1≤n≤1000),下面n行为n个字符串(字符串长度≤100),字符串中只含有大小写字母。输出描述:数据......
  • 【字典树/trie树】实现高效插入和查询字符串的数据结构
    本文是https://www.acwing.com/problem/content/description/837/的总结,有兴趣可以做做字典树的实现依赖于树结构,有两种操作,1是插入字符串,2是查找字符串。使用idx维护最新的结点下标。如下图,假设我们维护一个 可以看到,我们维护了一个树形结构储存了左边的字符串,但......
  • 【Python基础】dict(字典)
    简介介绍dictionary(字典)是除列表以外Python之中最灵活的数据类型字典同样可以用来存储多个数据通常用于存储描述一个物体的相关信息和列表的区别列表是有序的对象集合字典是无序的对象集合字典用{}定义字典特性*字典使用键值对存储数据,键值......
  • Trie字符串统计题解
    维护一个字符串集合,支持两种操作:"Ix"向集合中插入一个字符串x;"Q×"询问一个字符串在集合中出现了多少次。共有N个操作,输入的字符串总长度不超过105,字符串仅包含小写英文字母。输入格式第一行包含整数N,表示操作数。接下来N行,每行包含一个操作指令,指令为"l×"或"Q×"中的一种。......
  • 动态规划 多源路径 字典树 LeetCode2977:转换字符串的最小成本
    涉及知识点动态规划多源最短路径字典树题目给你两个下标从0开始的字符串source和target,它们的长度均为n并且由小写英文字母组成。另给你两个下标从0开始的字符串数组original和changed,以及一个整数数组cost,其中cost[i]代表将字符串original[i]更改为字符......