字典树(模板)

字典树:很强大的数据结构,实现多个字符串的查找、对其个数的纪录以及对其子串的查询。

这里讲下建树过程:

 准备:MAX 记录总节点数目

char str[1010][50];//输入的字符串集 
int ch[MAX][30];//字典树的实现 
int word[MAX];//记录当前节点下有多少个单词 
int val[MAX];//标记单词节点 
int sz;//节点数 

初始化:

void init()
{
    sz = 1;
    memset(ch[0], 0, sizeof(ch[0]));
    memset(word, 0, sizeof(word));
} 

返回字符的ascll码值:

int idx(char x)
{
    return x - 'a';
}

插入字符串:

void insert(char *s)
{

	int i, j, len = strlen(s);
	int u = 0;//根节点开始 
	for(i = 0; i < len; i++)
	{

		int c = idx(s[i]);
		if(!ch[u][c])//当前节点不存在 
		{

			memset(ch[sz], 0, sizeof(ch[sz]));//初始化新节点 
			val[sz] = 0;
			ch[u][c] = sz++; //节点数增一 

		}
		u = ch[u][c];
		word[u]++;//单词数加一 

	}
	val[u] = 1;//标记单词节点 
}

查找以字符串s为前缀的字符串 在字符串集中有多少个

int findnum(char *s)//查询以该s为前缀的字符串有多少个  
{  

    int i, j, l = strlen(s);  
    int u = 0;  
    for(i = 0; i < l; i++)  
    {  
        int c = idx(s[i]);  
        if(!ch[u][c]) return 0;//不存在   
        u = ch[u][c];  
    }  
    return word[u];  
}  

查找并输出字符串在串集里面唯一确定的最短前缀 

void findprefix(char *s)
{
    int i, j, len = strlen(s);
    int u = 0;
    for(i = 0; i < len; i++)
    {
        int c = idx(s[i]);
        u = ch[u][c];//继续查找 
        printf("%c", s[i]);
        if(word[u] == 1)//节点下只有一个单词 
        return ;
    } 
} 

判断该字符串是不是串集里某个字符串前缀 :

bool judgeprefix(char *s)
{
    int i, j, len = strlen(s);
    int u = 0;
    for(i = 0; i < len; i++)
    {
        int c = idx(s[i]);

		u = ch[u][c];//继续查找 

		if(word[u] == 1)//不是任何字符串的前缀 

		return true;

	} 

	return false;//是前缀 
} 

判断字符串是否由串集里的两个字符串构成: 假设该字符串分s1,s2两部分,这里只实现s1的查找

bool finds(char *s)
{
    int i, j, len = strlen(s);
    int u = 0;
    for(i = 0; i < len; i++)
    {
        int c = idx(s[i]);
        if(!ch[u][c])//不存在节点  
        return false;
        u = ch[u][c];
    }
    return val[u];//最后一个是不是单词节点 

}

大模板:

#include <cstdio>
#include <cstring>
#define MAX 50000+10
using namespace std;
char str[1010][50];//输入的字符串集 
int ch[MAX][30];//字典树的实现 
int word[MAX];//记录当前节点下有多少个单词 
int val[MAX];//标记单词节点 
int sz;//节点数 
int idx(char x)
{
    return x - 'a';
}
void init()
{
    sz = 1;
    memset(ch[0], 0, sizeof(ch[0]));
    memset(word, 0, sizeof(word));
} 
void insert(char *s)
{
    int i, j, len = strlen(s);
    int u = 0;//根节点开始 
    for(i = 0; i < len; i++)
    {
        int c = idx(s[i]);
        if(!ch[u][c])//当前节点不存在 
        {
            memset(ch[sz], 0, sizeof(ch[sz]));//初始化新节点 
            val[sz] = 0;
            ch[u][c] = sz++; //节点数增一 
        }
        u = ch[u][c];
		word[u]++;//单词数加一 
	}
	val[u] = 1;//标记单词节点 

}
//查找以字符串s为前缀的字符串 在字符串集中有多少个
int findnum(char *s)//查询以该s为前缀的字符串有多少个  
{  
    int i, j, l = strlen(s);  
    int u = 0;  
    for(i = 0; i < l; i++)  
    {  
        int c = idx(s[i]);  
        if(!ch[u][c]) return 0;//不存在   
        u = ch[u][c];  
    }  
    return word[u];  
}  
//查找并输出字符串在串集里面唯一确定的最短前缀 
void findprefix(char *s)
{

	int i, j, len = strlen(s);
	int u = 0;
	for(i = 0; i < len; i++)
	{

		int c = idx(s[i]);
		u = ch[u][c];//继续查找 
		printf("%c", s[i]);
		if(word[u] == 1)//节点下只有一个单词 
		return ;
	} 
} 

//判断该字符串是不是串集里某个字符串前缀  

bool judgeprefix(char *s)
{

	int i, j, len = strlen(s);
	int u = 0;
	for(i = 0; i < len; i++)
	{

		int c = idx(s[i]);
		u = ch[u][c];//继续查找 
		if(word[u] == 1)//不是任何字符串的前缀 
		return true;
	} 
	return false;//是前缀 

} 

//判断字符串是否由串集里的两个字符串构成:假设该字符串分两部分,这里只实现一部分的查找

bool finds(char *s)
{

	int i, j, len = strlen(s);
	int u = 0;
	for(i = 0; i < len; i++)
	{

		int c = idx(s[i]);
		if(!ch[u][c])//不存在节点  
		return false;
		u = ch[u][c];
	}

	return val[u];//最后一个是不是单词节点 
}

 

0-1字典树:

0-1字典树主要用于解决求异或最值的问题,0-1字典树其实就是一个二叉树,和普通的字典树原理类似,只不过把插入字符改成了插入二进制串的每一位(0或1)。

下面先给出0-1字典树的简单模板:

LL val[32 * MaxN]; //点的值 
int ch[32 * MaxN][2]; //边的值 
int tot; //节点个数 
void add(LL x) { //往 01字典树中插入 x 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
        int v = (x >> i) & 1;
        if(!ch[u][v]) { //如果节点未被访问过 
            ch[tot][0] = ch[tot][1] = 0; //将当前节点的边值初始化 
            val[tot] = 0; //节点值为0,表示到此不是一个数 
            ch[u][v] = tot++; //边指向的节点编号 

        }
        u = ch[u][v]; //下一节点 
    }
    val[u] = x; //节点值为 x,即到此是一个数 

}
LL query(LL x) { 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
    int v = (x >> i) & 1;//利用贪心策略,优先寻找和当前位不同的数 
    if(ch[u][v^1]) u = ch[u][v^1];
    else u = ch[u][v];
}
    return val[u]; //返回结果 

}

不难发现以下事实:

  • 01字典树是一棵最多32层的二叉树,其每个节点的两条边分别表示二进制的某一位的值为 0 还是为 1。将某个路径上边的值连起来就得到一个二进制串。
  • 节点个数为 1 的层(最高层,也就是根节点)节点的边对应着二进制串的最高位,向下的每一层逐位降低。
  • 以上代码中,ch[i] 表示一个节点,ch[i][0] 和 ch[i][1] 表示节点的两条边指向的节点,val[i] 表示节点的值。
  • 每个节点主要有4个属性:节点值、节点编号、两条边指向的下一节点的编号。
  • 节点值 val 为 0时表示到当前节点为止不能形成一个数,否则 val[i] = 数值。
  • 节点编号在程序运行时生成,无规律。
  • 可通过贪心的策略来寻找与 x 异或结果最大的数,即优先找和 x 二进制的未处理的最高位值不同的边对应的点,这样保证结果最大。

复杂度:O(32*n)

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值