420 likes | 546 Views
4. 3 串的模式匹配算法 ( 知识点三 ). 子串定位运算又称为模式匹配或串匹配,此运算的应用非常广泛。例如,文本编辑程序中,经常要查找某一特定单词出现的位置。解此问题的有效算法能极大地提高文本编辑程序的响应性能。 串的模式匹配定义 : 在主串中寻找子串在串中的位置。在模式匹配中,子串称为 模式串 ,主 串称为 目标串 。. 这是串的一种重要操作,很多软件,若有 “ 编辑 ” 菜单项的话,则其中必有 “ 查找 ” 子菜单项。. 4. 3.1 求子串位置的定位函数 Index(S,T,pos).
E N D
4. 3 串的模式匹配算法(知识点三) • 子串定位运算又称为模式匹配或串匹配,此运算的应用非常广泛。例如,文本编辑程序中,经常要查找某一特定单词出现的位置。解此问题的有效算法能极大地提高文本编辑程序的响应性能。串的模式匹配定义:在主串中寻找子串在串中的位置。在模式匹配中,子串称为模式串,主串称为目标串。 这是串的一种重要操作,很多软件,若有“编辑”菜单项的话,则其中必有“查找”子菜单项。
4. 3.1 求子串位置的定位函数 Index(S,T,pos) Index(S,T,pos)称为模式匹配(S为主串, T为模式串); 初始条件:S和T存在,T是非空串, 1 <= pos <= (S的长度)。 操作结果:若主串S中存在和串T相同的(模式串)子串,则返回它在主串S中第pos个字符之后第一次出现的位置;否则返回0。 例如:对某文本进行编辑时,可以运用如下步骤: (1)编辑;(2)查找 ——“输入查找文本(字符串)”;(3)找出对应 的串
T串 defghijk pos=4 Abcdefghijklmnopqrstuvw S串 按照上述主串S和子串T求子串位置的定位函数 Index(S,T,pos)的返回值是4
p79算法4.5 int Index (SString S, SString T, int pos) { // 返回子串T在主串S中第pos个字符之后的位置。若不存在, // 则函数值为0。其中,T非空,1≤pos≤StrLength(S)。 i = pos; j = 1; while (i <= S[0] && j <= T[0]) { if (S[i] == T[j]) { ++i; ++j;}// 继续比较后继字符 else{i = i-j+2; j = 1;}// 指针后退重新开始匹配 } if (j > T[0]) return i-T[0]; else return 0; } // Index
主串s="ababcabcacbab",模式T="abcac" 主串s 模式串T ="abcac" 模式串T ="abcac" 模式串T ="abcac" 核心语句 i=11 i=6 b if (S[i] == T[j]) { ++i; ++j; }// 继续比较后继字符 else { i = i-j+2; j = 1; } 返回值为11-5=6 因为T[0]=5 J=6
算法4.5 Index(S,T,pos) 即模式匹配的特点: 特点一:Index(S,T,pos) 算法的匹配方法简单,理解方便,适合一些文本编辑,效率较高; 特点二:Index(S,T,pos) 算法的匹配方法简单,理解方便,适合一些文本编辑,效率较高;正常情况下,时间复杂度为O(M+N); 特点 三:如果主串和子串存在多个零时,如: S=‘0000…1’{总共52个零};T=‘00000001’, 则出现多次重复的比较,即出现不等时, I 指针每次都回朔到i-1位置,这样浪费了大量的比较时间,整个匹配需要回朔45次,While循环语句的执行次数为46*8(index*m).
算法4.5 Index(S,T,pos) 即模式匹配的时间复杂性分析 因为Index(S,T,pos)是一种有回溯的模式匹配算法 ; 所以,在最坏情况下的时间复杂度是O(n*m)。
4. 3.2 模式匹配的一种改进算法 模式匹配的一种改进算法是D.E.Knuth 与V.R.Pratt和J.H.Morris同时发现的,因此,称该算法为克努特-莫里斯-普拉特算法(简称为KMP算法)。在串匹配算法中又称 KMP模式匹配算法。 KMP算法优点:可以在O(M+N)的时间复杂度内完成模式匹配操作,即对Index(S,T,pos)模式匹配算法的改进,取消了主串的回溯 。 KMP算法基本思想:每当匹配过程中出现字符比较不等时,i不回溯。
a b a b c a b c a c b a b a b a b c a b c a c b a b KMP模式匹配算法图示-1 i 第 1 趟 i=3,j=3时,失败即 s3 ≠ t3;; S1=t1 ; s2=t2; 因为t1≠t2;所以t1≠s2 a b c a c j 第 3 趟 a b c a c
i a b a b c a b c a c b a b a b a b c a b c a c b a b 第 4 趟 j a b c a c KMP模式匹配算法图示-2 第 3 趟 a b c a c i=7,j=5失败s4=t2;t1≠t2 ∴t1≠s4
i a b a b c a b c a c b a b a b a b c a b c a c b a b 第 5 趟 j a b c a c KMP模式匹配算法图示-3 第 3 趟 a b c a c i=7,j=5失败s5=t3;t1≠t3 ∴t1≠s5
i a b a b c a b c a c b a b a b a b c a b c a c b a b 第 6 趟 j a b c a c KMP模式匹配算法图示-4 第 3 趟 a b c a c i=7,j=5失败s5=t3;t1≠t3 ∴t1≠s5 匹配成功
KMP模式匹配算法小结-1 (1)结论: i可以不回溯,模式向右滑动到的新比较起点k ,并且k 仅与模式串T有关! (2)需要讨论两个问题: ①如何由当前部分匹配结果确定模式向右滑动的新比较起点k? ②模式应该向右滑多远才是最高效率的?
i j S="a b a b caa bc c b a b" (1)模式滑动到第k个字符,有p1~pk-1=Si-(k-1)~Si-1 (2)再观察失配时,有pj-(k-1) ~pj-1 =Si-(k-1) ~Si-1 两式联立可得:p1~pk-1=pj-(k-1)~pj-1 Si-(k-1)... si-1 当 si≠ pj失匹时 p="a bc a a ba b c" P1 --pk-1 Pj-(k-1) Pj-1
i i j k S="a b a b caa bc c b a b" S="a b a b ca abc c b a b" p="a bc a c" KMP模式匹配算法小结-2 关注部分匹配时的重要特征—— p="a bc a a b a b c" 假设:主串为’S1S2,…,Sn’ 模式串为’p1,p2,…,pn’ 当匹配过程中,产生失配(即si<> pj)时,模式串“向右滑动多远?”即主串中第i个字符(i指针不回溯)应与模式串中哪个字符再比较?
假设:这时应与模式串中第k个(k<j) 个字符继续比较,则模式串中第k-1个字符的子串必须满足下列关系式(教材P81的4-2),且不可能存在k’>k满足下列关系式(4-2): 模式滑动到第k个字符,有p1~pk-1=Si-(k-1)~Si-1…(4-2) 而已经得到的“部分匹配”的结果是: pj-(k-1) ~pj-1 =Si-(k-1) ~Si-1 …(4-3) 将(4-2) 和(4-3)两式联立可得:p1~pk-1=pj-(k-1)~pj-1 …(4-4) 反之,若模式串中存在满足式(4-4)的两个子串,则当匹配过程中,主串第I 个字符与模式串中第j 个字符比较不等时,仅需将模式向右滑动至模式中第k个字符和主串中第i个字符对齐,这时,模式串中k-1个字符‘p1~pk-1 ’必定与主串中第i个字符之前的k-1的子串‘Si-(k-1)~Si-1 ’ 相等,因此,匹配仅需从第k个字符与主串中第i个字符比较起继续进行。
p1…pk-1=pj-(k-1) …pj-1的物理意义是什么? 模式应该向右滑多远才是最高效率的? KMP模式匹配算法小结-3 p1…pk-1=pj-(k-1) …pj-1说明了什么? (1) k与j具有函数关系,由当前失配位置 j ,可以计算出滑动位置 k(即比较的新起点); (2)滑动位置k仅与模式串T有关。 从第1位往右 经过k-1位 从j-1位往左 经过k-1位 k=max { k |1<k<j且p1…pk-1=pj-(k-1) …pj-1 }
KMP模式匹配算法小结-4 令k = next[ j ],则: 0 当j=1时 //不比较 max { k | 1<k<j 且p1…pk-1=pj-(k-1) …pj-1 } 1 其他情况 next[ j ]= next[j]函数表征着模式T中最大相同首子串和尾子串(真子串)的长度。 可见,模式中相似部分越多,则next[j]函数越大,它既表示模式 T 字符之间的相关度越高,模式串向右滑动得越远,与主串进行比较的次数越少,时间复杂度就越低。
KMP模式匹配算法中next[j]的计算方法 • 计算next[j]的方法: • 当j=1时,next[j]=0;//next[j]=0表示根本不进行字符比较 • 当j>1时,next[j]的值为:模式串的位置从1到j-1构成的串中所出现的首尾相同的子串的最大长度加1。 • 当无首尾相同的子串时next[j]的值为1。next[j]=1表示从模式串头部开始进行字符比较
KMP算法的时间复杂度可达到O(m+n) 其改进在于:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。 在下面将要解决的问题是:如何求出下一个j的位置即next[j]=?,而不回溯i指针。
目前需要解决的问题是—— (1)若i不需回溯,则 j应该退回到何处? (2)设退回到 next[j], 则next[j]=? (3)对于给定的模式串,如何求解next[j]是问题的关键。 (4)next[j]与s串无关,只与t串有关
0 当 j=1 • max { k | 1< k < j且 • “p1p2 …pk1 "=“pjk+1 pjk+2 …pj1 "} • (相同的前缀子串与后缀子串的最大长度+1) • 其他 Next [ j ]= Next [ j ]函数定义和计算模式如下:
例: • 设有模式串T=“abaabcac“, 计算next[j] j 1 2 3 4 5 6 7 8 模式串 a b a a b c a c next[j] 0 1 1 2 2 3 1 2
算法4.6 P82 int Index_KMP(SString S, SString T, int pos) { // 1≤pos≤StrLength(S) i = pos; j = 1; while (i <= S[0] && j <= T[0]) { if (j == 0 || S[i] == T[j]) { ++i; ++j; } // 继续比较后继字符 else j = next[j]; // 模式串向右移动 } if (j > T[0]) return i-T[0]; // 匹配成功 else return 0; } // Index_KMP 主串S=“acabaabaabcacaabc” 模式串T=“abaabc”
begin i = pos; j = 1; N N 返回0 i <= S[0] && j <= T[0])? (j > T[0])? y y return i-T[0] N (j == 0 || S[i] == T[j])? j = next[j] end y i=i+1 ; j=j+1;
KMP算法是在已知模式串的next函数值的基础上执行的,那么,如何求得模式串的next函数值呢? 从上述讨论可见: k 的确定方法 当比较到模式第 j 个字符失配时,k ( next[j] )的值与模式的前 j 个字符有关,与目标无关。 此函数值仅取决于模式串本身而和相匹配的主串无关。我们可从分析其定义出发用递推的方法求得next函数值。
求next函数值的过程是一个递推过程,分析如下-1:求next函数值的过程是一个递推过程,分析如下-1: 已知: next[1] = 0; (4-6) 假设: next[j] = k; 有 : ‘P1P2……Pk-1‘=‘Pj-k+1Pj-k+2……Pj-1’(4-7) 其中k为满足1<k<j的某个值,且不可能存在k’>k满足等式(4-7)。这时, next[j] = ?可能存在两种情况: 情况一: 若:Pk= Pj,则表明在模式串中存在, ‘P1P2……Pk ‘=‘Pj-k+1Pj-k+2……Pj ’(4-8) 且不可能存在k’>k满足等式(4-8),也就是说next[j+1] = k+ 1,即 next[j+1] = next[j] + 1 = k + 1 情况二: Pk<> Pj , 则表明在模式串中 ‘P1P2……Pk ’ < > ‘Pj-k+1Pj-k+2……Pj ’ 这时,可将求next函数值问题看作是一个模式匹配问题,整个模式串既是主串又是模式串。而当前在匹配过程中,已经有:
求next函数值的过程是一个递推过程,分析如下-2:求next函数值的过程是一个递推过程,分析如下-2: 有 : ‘Pj-k-1‘=‘P1’, Pj-k-2= P2,‘Pj-1 ‘=‘Pk’ 则当Pk<> Pj时, 应将模式向右滑动至模式中的第next[k]个字符和主串中第j 个字符相比较。若next[k]=k’,且pj=pk’,则说明在主串中第j+1个字符之前存在一个长度为k’ (即next[k]最长子串,和模式串中从首字符起长度为k’的子串相等,即(P83) 有 : ‘P1P2……Pk’‘=‘Pj-k’…,Pj’(1<k’<k<j)(4-10) 就是说, next[j+1]=k’+1(4-11) 同理,若Pj<> Pk’,则将模式继续向右滑动直至将模式next[k’] 个字符和pj对齐,…,以此类推,直至pj和模式中某个字符匹配成功或者不存在任何k’(1<k’<j)满足等式(4-10) ,则 next [j+1] = 1(4-12)
根据下边的图4.6 中的模式串,已经求出前 6个字符的next函数值,现求next[7]=?, 因为next[6]=3,又因p6<>p3, 则需比较p6和p1(因为next[3]=1,这相当于将子串模式向右滑动, 由于p6<>p1 ,而且next[1]=0,所以,next[7]=1,而p7=p1 ,则next[8]=2。
下面再用图示法举例说明求next函数值的匹配过程:下面再用图示法举例说明求next函数值的匹配过程: 设 next[j]=k j=5,k=2 则 next[5]=2 P1 …Pk-1 =Pj-k+1 …Pj-1 P1 =P4 若 (1)Pj =Pk P2 =P5 有 P1 P2 =P4 P5 (P1 …Pk-1 =Pj-k+1 …Pj-1) j-1=5 ∴j=6 k-1=2 next[6]=3 next[j+1]= next[j]+1 = k+1 模式串的匹配过程 j=5 j+1=6 目标aba abc a c 模式 a b a 0 1 1 2 2 3 J=5 J+1=6
求next函数值的过程是一个递推过程,分析如下:求next函数值的过程是一个递推过程,分析如下: 已知: next[1] = 0; 假设: next[j] = k; 有 : ‘P1P2……Pk-1‘=‘Pj-k+1Pj-k+2……Pj-1‘ (2)若: Pj Pk 则: 需往前回朔,检查 Pj=P ? 这实际上也是一个匹配的过程; 不同在于:部分主串和模式串是同一个串
综上所述,根据分析结果式(4-6)、(4-9)、(4-11)和(4-12),仿照KMP算法,可以求出next函数值的算法,如算法4.7(p83)所示:综上所述,根据分析结果式(4-6)、(4-9)、(4-11)和(4-12),仿照KMP算法,可以求出next函数值的算法,如算法4.7(p83)所示: void get_next(SString &T, int &next[] ) { // 求模式串T的next函数值并存入数组next i = 1; next[1] = 0; j = 0; while(i < T[0]){ if (j = 0 || T[i] == T[j]) {++i; ++j; next[i] = j; } else j = next[j];} } // get_next 算法4.7的时间复杂度为 O(m),通常模式串的长度要比主串的长度n 要小得多,因此,对整个匹配算法来讲,所增加的这点时间是值得的。
还有一种特殊情况需要考虑: 例如: S = aaabaaaab T = aaaab next[j]=01234 nextval[j]=00004
void get_nextval(SString &T, int &nextval[]) { i = 1; nextval[1] = 0; j = 0; while (i < T[0]) { if (j = 0 || T[i] == T[j]) { ++i; ++j; if (T[i] != T[j]) next[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } } // get_nextval
4. 4 串操作应用举例 • 4. 4.1 文本编辑 • 4. 4.2 建立词索引表
4. 4.1 文本编辑 为了管理文本串中的页和行,在文本编辑时,编辑软件先为文本串建立相应的页表和行表,页表的每一项列出页号和该页的起始行号,行表的每一项则指示每一行的行号、起始地址和该行子串的长度。行表和页表与串是分开存储的。
将如下一段源程序中的每行语句的起始地址和长度信息存储在100开始的地址中;具体的源程序存放在201开始的内存中:将如下一段源程序中的每行语句的起始地址和长度信息存储在100开始的地址中;具体的源程序存放在201开始的内存中: Main(){ float a,b,max; scanf(“%f,%f”,&a,&b); if a>b max=a; else max=b; }
4. 4.2 建立词索引表 p86 具体步骤: (1)建立图书的书号及书名表的数据结构; (2)根据图书名中的关键词,建立书号索引及对应的的数据结构; (3)从图书文件中读入一个书目串; (4)从目串中提取所有关键词插入词表; (5)对词表的每一个关键词,在索引表中进行查找,并作相应的插入操作。 重复上述的(3)、(4)和(5)。 具体算法见P87-89。
第四章学习要点 1、 熟悉串基本操作的定义,并能利用这些基本操作来实现串的其它各种操作的方法。 2、 熟练掌握在串的定长顺序存储结构上实现串的各种操作的方法。 3、了解串的堆存储结构以及在其上实现串操作的基本方法。
4、理解串匹配的KMP算法,熟悉NEXT函数的定义,学会手工计算给定模式串的NEXT函数值和改进的NEXT函数值。4、理解串匹配的KMP算法,熟悉NEXT函数的定义,学会手工计算给定模式串的NEXT函数值和改进的NEXT函数值。 5、了解串操作的应用方法和特点。