KMP算法2.0+Trie字符串集合

本文详细介绍了KMP算法的2.0版本,包括改进后的Next数组构造方法,以及两种不同的实现形式,适用于字符串匹配问题。通过实例和模板展示了如何利用Next数组进行高效匹配,并探讨了字典树的巧妙数组存储技巧在Trie树中的应用。
摘要由CSDN通过智能技术生成

Kmp算法

最近在学算法,发现一个更好的kmp算法的模板,在保留之前那篇关于kmp
算法1.0的博客的基础下决定整理分析kmp2.0版

这一段专门提出来作为模板记录:

    for (int i = 2, j = 0; i <= n; i++)
	{
		while (j && pat[j + 1] != pat[i]) j = Next[j];
		if (pat[j + 1] == pat[i]) j++;
		Next[i] = j;
	}
	for (int i = 1, j = 0; i <= m; i++)
	{
		while (j && pat[j + 1] != str[i]) j = Next[j];
		if (pat[j + 1] == str[i]) j++;
		if (j == n)
		{
			cout << i - n << " ";
			j = Next[j];
		}
	}

题目链接–KMP字符串
完整代码

#include<iostream>
using namespace std;
const int N = 1e5 + 5, M = 1e6 + 5;
char str[M], pat[N];
int Next[N];
int main()
{
	int n, m;
	cin >> n >> pat+1 >> m >> str + 1;//字符串都从第1位开始存储
	for (int i = 2, j = 0; i <= n; i++)
	{
	//j==0的时候不用考虑回溯
		while (j && pat[j + 1] != pat[i]) j = Next[j];
		if (pat[j + 1] == pat[i]) j++;
		Next[i] = j;
	//构造Next数组,Next数组存储的是1~i的最大前后缀匹配的长度
	//j+1指向的是前缀的终点,i指向的是后缀的终点(这一句是关键)
	//这一段自己模拟走一步就懂了
	}
	for (int i = 1, j = 0; i <= m; i++)
	{
		while (j && pat[j + 1] != str[i]) j = Next[j];
		if (pat[j + 1] == str[i]) j++;
		if (j == n)
		{
			cout << i - n << " ";
			j = Next[j];//网上回溯
		}
	}
	return 0;
}

坐标从0开始版本:
思路是一样的,不过是稍微改了点东西
求next数组的j从-1开始,i从1开始,j==-1的时候不需要回溯,kmp匹配的j变成-1,i变成0。输出的条件是j==length-1
其他没差。

#include<iostream>
using namespace std;
int Next[100005];
int main()
{
	string pat, str;
	int n, m;
	cin >> n >> pat >> m >> str;
	Next[0] = -1;
	for (int j = -1, i = 1; i < pat.length(); i++) {
		while (j != -1 && pat[j + 1] != pat[i])
			j = Next[j];
		if (pat[j + 1] == pat[i])
			j++;
		Next[i] = j;
	}
	for (int j = -1, i = 0; i < str.length(); i++) {
		while (j !=-1 && pat[j + 1] != str[i])
			j = Next[j];
		if (pat[j + 1] == str[i])
			j++;
		if (j == pat.length() - 1) {
			cout << i - j << " ";
			j = Next[j];
		}
	}
	return 0;
}

在这里插入图片描述

Tire树(字典树)

一般来说字典树的创建都是通过指针,但当面对数据范围不是很大的时候,可以考虑用数组存储,本题的数组存储法更为巧妙
例题链接–Trie字符串统计

int son[N][26], cnt[N], idx;

先解释一下这两个数组和idx的含义:
son是用来存储当前节点的孩子节点的位置的,cnt是用来存储以当前节点为结尾的字符串的个数,idx是用来更新孩子节点位置的
这里的idx用的最为巧妙:
仔细观察数组发现存储Trie树的数组只是一个二维数组,但按照一般Trie树数组存储的方法,每一个节点都有一个长度为26的数组指向该节点的子节点,比如“abc”,根节点的孩子数组长度为26,其中a,b,c是已经存储的,接着到a节点,a节点有一个孩子数组长度也是26,b,c相同,所以对于一个两层的Trie树需要的空间大小是26+26*26
在这里插入图片描述
但这题的idx却是一直递增的,if (!son[p][u]) son[p][u] = ++idx;表示要存储的这个新的字符在树里面是不存在的,son[p][u] = ++idx;就表示对son[p][u]这个节点分配新的空间,++idx表示son[p][u]的孩子节点的指向。
比如:idx初始为0,插入 “abcde” “abcef”,两个字符串前三个字符存储的位置是一样的,在第0,1,2层,a,b的idx(孩子节点的位置)都为1 2,u=str[i]-‘a’,所以第一个字符串‘a’对应为第0层,idx++,那么a的孩子就在第一层
/son[0][0]=1,表示第0层的’a’字符对应的孩子节点在第一层
其他两个同理,这时候第一个字符串到d了,开始时son[[3][‘d’-‘a’]==0,idx++,开辟新的空间,表示该节点的孩子节点的层数为4。这是第一个字符串的操作,那么对于第二个字符串呢,当字符指向e时(str[3]),e字符在第三层并没有被存储,经过第一个字符串的存储,idx已经为5,这个5是第一个字符串结尾字符e的孩子数组指向的位置,当面对第二个字符串的e,idx++,所以e的孩子数组的指向在第6层,与第一个字符串中字符d(str[3])孩子数组的指向位置不是同一个。
在这里插入图片描述
cnt[N]存储的以当前节点为结尾字符串的个数,这个理解了idx的作用和整个结构的存储方式就不难理解。比如"abc",就是cnt[3]++,在存储”abe“,就是cnt[4]++.

模板:

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];
}

完整代码:

#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int son[N][26], cnt[N], idx;
void Insert(char str[]);
int Query(char str[]);
int main()
{
	int n;
	cin >> n;
	while (n--) {
		char c, str[N];
		cin >> c >> str;
		if (c == 'I') Insert(str);
		else cout << Query(str) << endl;
	}
	return 0;
}
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];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值