导航:首页 > 源码编译 > 模式匹配随机算法例子

模式匹配随机算法例子

发布时间:2023-12-12 09:57:05

A. 那些经典算法:AC自动机

第一次看到这个名字的时候觉得非常高级,深入学习就发现,AC就是一种多模式字符串匹配算法。前面介绍的BF算法,RK算法,BM算法,KMP算法都属于单模式匹配算法,而Trie树是多模式匹配算法,多模式匹配算法就是在一个主串中查找多个模式串,举个最常用的例子,比如我们在论坛发表评论或发帖的时候,一般论坛后台会检测我们发的内容是否有敏感词,如果有敏感词要么是用***替换,要么是不让你发送,我们评论是通常是一段话,这些敏感词可能成千上万,如果用每个敏感词都在评论的内容中查找,效率会非常低,AC自动机中,主串会与所有的模式串同时匹配,这时候就可以利用AC自动机这种多模式匹配算法来完成高效的匹配,

AC自动机算法是构造一个Trie树,然后再添加额外的失配指针。这些额外的适配指针准许在查找字符串失败的时候进行回退(例如在Trie树种查找单词bef失败后,但是在Trie树种存中bea这个单词,失配指针会指向前缀be),转向某些前缀分支,免于重复匹配前缀,提高算法效率。
常见于IDS软件或病毒检测软件中病毒特征字符串,可以构建AC自动机,在这种情况下,算法的时间复杂度为输入字符串的长度和匹配数量之和。

假设现有模式字符串集合:{abd,abdk, abchijn, chnit, ijabdf, ijaij} 构建AC自动机如下:

说明:

1)当前指针curr指向AC自动机的根节点:curr=root。
2)从文本串中读取(下)一个字符。
3)从当前节点的所有孩子节点中寻找与该字符匹配的节点:

4)若fail == null,则说明没有任何子串为输入字符串的前缀,这时设置curr = root,执行步骤2.
若fail != null,则将curr指向 fail节点,指向步骤3。
理解起来比较复杂,找网上的一个例子,假设文本串text = “abchnijabdfk”。
查找过程如下:

说明如下:
1)按照字符串顺序依次遍历到:a-->b-->c-->h ,这时候发现文本串中下一个节点n和Trie树中下一个节点i不匹配,且h的fail指针非空,跳转到Trie树中ch位置。
注意c-->h的时候判断h不为结束节点,且c的fail指针也不是结束节点。
2)再接着遍历n-->i,发现i节点在Trie树中的下一个节点找不到j,且有fail指针,则继续遍历,
遍历到d的时候要注意,d的下一个匹配节点f是结束字符,所以得到匹配字符串:ijabdf,且d的fail节点也是d,且也是结束字符,所以得到匹配字符串abd,不过不是失败的匹配,所以curr不跳转。

先将目标字符串插入到Trie树种,然后通过广度有限遍历为每个节点的所有孩子节点找到正确的fail指针。
具体步骤如下:
1)将根节点的所有孩子节点的fail指针指向根节点,然后将根节点的所有孩子节点依次入队列。
2)若队列不为空:
2.1)出列一个字符,将出列的节点记为curr,failTo表示curr的
fail指针,即failTo = curr.fail 。
2.2) 判断curr.child[i] == failTo.child[i]是不是成立:
成立:curr.child[i].fail = failTo.child[i]
因为当前字符串的后缀和Tire树的前缀最长部分是到fail,
且子字符和failTo的下一个字符相同,则fail指针就是
failTo.child[i]。
不成立: 判断failTo是不是为null是否成立:
成立: curr.child[i].fail = root = null。
不成立: failTo = failTo.fail 继续2.2
curr.child[i]入列,再次执行步骤2)。
3)队列为空结束。

每个结点的fail指向的解决顺序是按照广度有限遍历的顺序完成的,或者说层序遍历的顺序进行,我们根据父结点的fail指针来求当前节点的fail指针。

上图为例,我们要解决y节点的fail指针问题,已经知道y节点的父节点x1的fail是指向x2的,根据fail指针的定义,我们知道红色椭圆中的字符串序列肯定相等,而且是最长的公共部分。依据y.fail的含义,如果x2的某个孩子节点和节点y表示的表示的字符相等,y的fail就指向它。
如果x2的孩子节点中不存在节点y表示的字符。由于x2.fail指向x3,根据x2.fail的含义,我们知道绿色框中的字符序列是相同的。显然如果x3的某个孩子和节点y表示字符相等,则y.fail就指向它。

如果x3的孩子节点不存在节点y表示的字符,我们重复这个步骤,直到xi的fail节点指向null,说明我们达到顶层,只要y.fail= root就可以了。
构造过程就是知道当前节点的最长公共前缀的情况下,去确定孩子节点的最长公共前缀。

下图中,每个节点都有fail虚线,指向根节点的虚线没画出,求图中c的孩子节点h的fail指向:

原图中,深蓝色的框出来的是已经确定fail指针的,求红色框中h节点的fail指针。
这时候,我们看下h的父亲节点c的fail指针指向,为ch中的c(这表示abc字符串的所有后缀bc和c和Trie树的所有前缀中最长公共部分为c),且这个c节点的孩子节点中有字符为h的字符,所以图中红色框中框出的h节点的fail指针指向 ch字符串中的h。

求红色框中i的fail指针指向,上图中,我们可以看到i的父亲节点h的指向为ch中的h,(也就是说我们的目标字符串结合中所有前缀和字符序列abch的所有后缀在Trie树中最长前缀为ch。)我们比较i节点和ch中的h的所有子节点,发现h只有一个n的子节点,所以没办法匹配,那就继续找ch中h的fail指针,图中没画出,那么就是它的fail指针就是root,然后去看root所有子节点中有没有和i相等的,发现最右边的i是和我们要找的i相等的,所以我们就把i的fail指针指向i,如后面的图。

B. 数据结构与算法——字符串匹配问题(KMP算法)

KMP算法也是比较着名的模式匹配算法。是由 D.E.Knuth,J.H.Morrs VR.Pratt 发表的一个模式匹配算法。可以大大避免重复遍历的情况。

如果使用暴风算法的话,前面五个字母完全相等,直到第六个字母 "f" "x" 不相等。如下图:

T = “abcdex”
j 123456
模式串 abcdex
next[j] 011111

T = "abcabx"
j 123456
模式串T abcabx
next[j] 011123

T = "ababaaaba"
j———————123456789
模式串T——— ababaaaba
next[j]————011234223

T = "aaaaaaaab"
j———————123456789
模式串T——— aaaaaaaab
next[j]————012345678

next数组其实就是求解字符串要回溯的位置
假设,主串S= “abcababca”;模式串T=“abcdex”,由以上分析得出next数组为011111,next数组意味着当主串与模式串不匹配时,都需要从第一个的位置重新比较。

KMP算法也是有缺陷的,比如主串S=“aaaabcde”,模式串T= “aaaaax”。next的数组就是012345;

当开始匹配时,当i= 5,j = 5时,我们发现字符"b"与字符“a”不相等,如上图,j = next[5] = 4;

由于T串的第二、三、四、五位置的字符都与首位“a”相等,那么可以用首位next[1]的值去取代与它相等的后续字符的next[j],那么next数组为{0,0,0,0,0,5};

在求解nextVal数组的5种情况

C. 串模式匹配算法(C语言)100分悬赏

第一个朴素算法:
1.普通的串模式匹配算法:
int index(char s[],char t[],int pos)
/*查找并返回模式串T在S中从POS开始的位置下标,若T不是S的子串.则返回-1.*/
{
int i,j,slen,tlen;
i=pos;j=0; //i,j分别指示主串和模式串的位置.
slen=strlen(s);tlen=strlen(t); //计算主串和模式串的长度.
while(i<slen && j<tlen)
{
if(s[i]==t[j]) {i++;j++;}
else {i=i-j+1;j=0;}
}
if(j>=tlen) return i-tlen;
return -1;
}

第二个KMP算法.该算法支持从主串的任意位置开始搜索.
2.KMP算法:
//求模式串的next函数.
void get_next(char *p,int next[])
{
int i,j,slen;
slen=strlen(p);i=0;
next[0]=-1;j=-1;
while(i<slen)
{
if(j==-1||p[i]==p[j]) {++i;++j;next[i]=j;}
else j=next[j];
}
}

//KMP模式匹配算法
int index_kmp(char *s,char *p,int pos,int next[])
/* 利用模式串P的NEXT函数,求P在主串S中从第POS个字符开始的位置*/
/*若匹配成功.则返回模式串在主串中的位置下标.否则返回-1 */
{
int i,j,slen,plen;
i=pos-1;j=-1;
slen=strlen(s);plen=strlen(p);
while(i<slen && j<plen)
{
if(j==-1||s[i]==p[j]) {++i;++j;}
else j=next[j];

D. 数据结构-串的模式匹配

串的模式匹配就是子串定位操作。给定两明亏个串s="s0 s1 ... s(n-1)"和t="t0 t1 ... t(m-1)"(其中n和m分别是串s和t的长度),在主串s中寻找子串t的过程称为模式匹配,t称为模式。如果在s中找到等于t的子串,则称匹配成功,返回t在s中的首次出现的下标位置;否则匹配失败,返回-1。

本文介绍三个串模式匹配算法,分别是简单回溯算法(Brute-Force,BF算法)、KMP算法、KMP算法的改进。

从主串s的第0个字符开始,与模式串t的第0个字符开始逐字符比较,不相同时回溯到模式串t的第0个和主串s的第1个字符,重新开始比较。以此类推,直到t的所有字符完成匹配,则匹配成功,否则匹配失败。

BF算法速度慢的原因是存在大量不必要的回溯,即在某一趟与t的匹配过程失败后,需要返回s串开始字符的下一字符重新开始比较,这对于某些模式串t来说是不必要的。例如,若s=12123123132,t=12313,在t与12 12312 3132中加粗子序列进行比较时,在 2 处发生失配,BF算法接下来将t与121 23123 132、1212 31231 32、12123 12313 2比较。由于t中的231、312与其开始的123并不相同,显然t与121 23123 132、1212 31231 32的比较是不必要的。

KMP算法就是利用模式串中与模式串开头部分子串的重复性来减少重复回溯,实现新一轮比较的直接跳转。 具体来说,KMP算法利用一个数组记录模式串中每一个字符前面有几个字符与模式串从头重复,在与s串比较失配时,直接跳转到重复子串的下一个字符继续比较,而不用跳转至模式串t的第0个字符。

算法步骤: ①计算跳转数组next。②利用KMP算法进行模式匹配。

next数组通过递推计算,即如果当前字符 t[j] 的前一个字符 t[j-1] 与其 next[j-1] 指向的字符 t[next[j-1]] 相同,意味着 t[j] 前的 next[j-1]+1 个字符与从 t[0] 到 t[next[j-1]] 的子串相同,因此 next[j]=next[j-1]+1 ;如果不相同,则递推至 t[next[j-1]] 的next值指向的字符,与 t[j-1] 比较,直到确认 t[j] 前与 t 串从头重复的数羡字符数,或者无重复字符标记为薯槐拍 0 。

注意此处的函数返回参数类型为int*,用于 返回一位数组 ,且返回的这个一位数组必须在函数中用static定义。

KMP算法进行模式匹配时,只需在回溯时将 j 指针赋值为 next[j] 。需要注意的是,若 next[j] 为 -1 ,则意味着 t[j] 前面没有与 t 从头重复的字符,且 t[j] 与 s[i] 失配,则 i 和 j 均加 1 。

考虑更特殊的模式串,还能进一步减少不必要的回溯次数。例如,s=111211112,t=11112,按照上述next的计算方式,next={-1,0,1,2,3}。当 i=3, j=3 时失配,此时 s[i]=2, t[j]=1 ,由于 next[j]=2 ,于是 j 跳转为 2 ,t=11 1 12与s=111 2 11112比较。由于 t[next[j]]=t[j] 也为 1 ,必然与 s[i]=2 不相同,显然这次回溯也不必要。

总结来说, 当失配的字符与待跳转的字符相同时,跳转一步并无意义,可再跳一步 ,即将当前字符置为跳转后字符的next值。

E. 随机化算法的举例

下面,我们就随机化问题,举一个例子:
一个长度在4..10的字符串中,需要判定是否可以在字符串中删去若干字符,使得改变后字符串符合以下条件之一:
(1)AAAA;(2)AABB;(3)ABAB;(4)ABBA。
例如:长度为6字符串“POPKDK”,若删除其中的“O”,“D”两个字母,则原串变为:“PPKK”,符合条件(2)AABB。
分析:
这道题很容易想到一种算法:运用排列组合:枚举每4个字母,然后逐一判断。算法是可行的,但是如果需要题目中加上一句话:需要判断n个字符串,且n<=100000,那么这样的耗时是不能让人忍受①的,因为在枚举的过程中,是非常浪费时间的。
(①:这里是指信息学中要求算法的普遍运算时间为:1000ms)
所以这道题有可能可以借助于随机化算法,下面我们来算一下在10个字符中取4个字符一共有多少种取法:C(4,10)=210。那么很容易得知,随机化算法如果随机300次,能得到的结果基本上就正确了(概率为1-(209/210)^300,约为0.76),而随机时的时间消耗是O(1),只需要判断没有随机重复即可,判重的时间复杂度也为O(1),并且最多随机300次,这样就可以有效地得到答案,最大运算次数为:O(300n),这是在计算机的承受范围内(1000ms)的。
从这里就能看出,随机化算法是一个很好的概率算法,但是它并不能保证正确,而且它单独使用的情况很少,大部分是与其他的算法:例如贪心、搜索等配合起来运用。 排序问题。快速排序是排序方法中较为便捷的方法之一,但是由于它极不稳定,最好的时候时间复杂度为O(n㏒n),这里的㏒是指以2为底的对数运算。最坏的时候能达到与普通排序方法一样的O(n^2)。
而制约快速排序的有两个:一是数据,越无序的数据,快排的速度越快;二是中间点的枚举。
因为两个制约条件都与随机有着不可分开的关系。
所以,在快速排序中加入随机化算法无疑是十分重要的。
运用在:
(1)数据读入时,随机排放数据位置。
(2)中间点的枚举进行多次随机化后决定。
这样就基本上将快速排序的时间复杂度维持在最好状态。

阅读全文

与模式匹配随机算法例子相关的资料

热点内容
海康摄像萤石云服务器 浏览:814
安卓手机怎么改安卓版名 浏览:147
雅思听力807词汇pdf 浏览:897
黄豆私人加密 浏览:192
java分钟转换小时 浏览:245
易语言服务器如何提高 浏览:591
网站主机服务器地址查看 浏览:859
算法学不会能当程序员吗 浏览:119
程序员技术交流研究 浏览:814
javaresponse文件 浏览:734
linuxrar压缩文件夹 浏览:218
魅蓝手机连接不上服务器怎么回事 浏览:379
工行app怎么改已绑定银行卡 浏览:533
oppo芯片程序员 浏览:602
oppok3应用怎么加密 浏览:327
电脑软盘怎么加密码 浏览:815
服务器光交换机有什么用 浏览:708
app上怎么拍蛙小侠 浏览:217
志高聊天app怎么下载 浏览:635
邮政app怎么不能扫付款码 浏览:559