字符串匹配自动机的算法原理

上一节,我们知道,如何构造一个有限状态机,用于字符串匹配,我们只给出了怎么做,这一节,我们详细说明一下,为什么要这么做,我们要从数学上验证上一节我们给出的算法逻辑是经得起考验的。

这里写图片描述

如上图所示,有限状态自动机有以下几个特点:
1. 它由一系列的状态节点组成,我们用Q来表示这些节点的集合
2. 状态机一开始就会处于初始状态,我们用 q0 来表示
3. 所以状态中,必有一个状态A Q 叫接收状态,例如上图的节点1.
4. 组成字符串的字符集 , 例如上图中,字符集只包含a,b两个字符。
5. 当状态机处于某个状态,接收到一个输入字符时,会跳转到另一个状态,这种跳转我们用一个函数 δ 来表示,例如,根据上图,当状态机处于状态0,输入是字符a时,状态转移到状态1,于是就有 (0, a) = 1

我们再引入一个函数 ϕ , 叫最终状态函数,它接收一个字符串,然后给出状态机读入该字符串后,最终会处于哪个状态,例如给定字符串”abba”,上面的状态机接收后,最终会处于状态1,于是就有 ϕ (“abba”) = 1, 如果给定的字符串是”aabb”, 状态机接收该字符串后,最终处于的状态是0,所以 ϕ (“aabb”) = 0

状态机一开始时,处于初始状态,也就是状态机什么都不接收时或接收空字符串时就处于初始状态,于是我们有:
ϕ ( ϵ ) = q0
假设w 是一个字符串,那么有:
ϕ (wa) = δ ( ϕ (w), a)
上面这个公式需要好好解释一下,假定w=”aabb”, wa = “aabb” + ‘a’ = “aabba”.

当状态机接收字符串”aabb”后,处于状态0,于是就有 ϕ (“aabb”) = 0.状态机接收字符串wa后所达到的最终状态,相当于先接收字符串w,达到状态0后,再接收最后的字符a,使得状态机从状态0,再进行一次跳转,根据上图,状态机处于状态0,输入字符为a时,跳转到状态1,于是有 δ (0, a) = 1, 又由于状态0是状态机接收字符串”aabb”后的最终状态,所以0 = ϕ (“aabb”), 代入上一个式子有 δ ( ϕ (“aabb”), a) = 1, 先接收字符串”aabb”,到达一个状态,然后再接收字符a到达另一个状态,这不就相当于字符串”aabba” = “aabb” + ‘a’, 所抵达的最终状态吗,所以就有:
ϕ (“aabba”) = δ ( ϕ (“aabb”), a).

数学推理是一个比较烧脑的过程,想必上面的解释会让不少同学抓耳挠腮一阵子。

假设要查找的字符串,我们用P来表示,对于给定一个文本T, 如果P 的前k个字符所组成的字符串是T的后缀的话,我们就定义:
σ (T) = k,

例如 P=”abcdefg”, T = “hhhhhhhha”, 那么P的前1个字符所组成的字符串”a”是T的后缀,所以 σ (T) = 1

T=”hhhhhhab”, 那么P的前两个字符组成的字符串”ab”构成T的后缀,于是 σ (T) = 2.

T=”hhhhhabc”, P的前3个字符组成的字符串”abc”构成T的后缀,于是有 σ (T) = 3

依次类推。

上一节我们构造的状态机是满足以下条件的:
1. 如果P 含有m个字符,那么状态机就有m+1个状态节点,他们分别为{0,1,2…m}, 并且初始状态 q0 = 0, 接收状态是m.
2. 当状态机处于状态q时,如果接收字符a, 那么状态机要跳转的下一个状态是:
δ (q, a) = σ ( Pq a)

上一节我们给出的代码有这么一段:

 private void makeJumpTable() {
       int m = P.length();
       for (int q = 0; q <= m; q++) {
           for (int k = 0; k < alphaSize; k++) {

               char c = (char)('a' + k);
               String Pq = P.substring(0, q) + c;


               int nextState = findSuffix(Pq);
               System.out.println("from state " + q + " receive input char " + c + " jump to state " + nextState);
               HashMap<Character, Integer> map = jumpTable.get(q);
               if (map == null) {
                   map = new HashMap<Character, Integer>();
               }

               map.put(c, nextState);
               jumpTable.put(q, map);
           }
       }

String Pq = P.substring(0, q) + c; 这一句代码的作用,其实就是构造字符串 Pq a, 代码findSuffix(Pq); 其实就是计算 σ ( Pq a).

如果我们能够证明,我们前一节构造的状态机满足:
ϕ ( Ti ) = σ ( Ti )

ϕ ( Ti ) 表示把文本T前i个字符构造的字符串输入到状态机后所达到的最终状态。

σ ( Ti ) 表示,从P的前k个字符可以组成字符串 Ti 的后缀,如果有某个i使得 ϕ ( Ti ) = m, 那么根据上面式子,字符串P将成为字符串 Ti 的后缀,而 Ti 又是字符串T的前缀,这不就意味着字符串P包含在T中了吗,所以,如果我们构造的状态机满足上面的等式,那么我们依次将T的字符输入到状态机中,当状态机跳转到状态m时,就表明字符串P包含在文本T中了。于是接下来我们将思考如何证明等式:

ϕ ( Ti ) = σ ( Ti )

定理1:
对给定的匹配字符串P, 以及文本x, 还有任意字符a, 我们有:
σ (xa) <= σ (x) + 1

令r = σ (xa), 如果r = 0 ,上面的等式明显是成立的。因为 σ (x)肯定是大于等于0的。如果r > 0, 也就是从P的第一个字符开始,连续r个字符所构成的字符串 Pr 能构成xa的后缀,xa = x + ‘a’,也就是字符串xa是以字符a结尾的, 那么 Pr 的最后一个字符也肯定是’a’, 如果我们同时将字符’a’从 Pr 和xa的末尾去掉,那么我们有 Pr1 是x的后缀,例如P=”bac”, x=”dddb”, 那么 P2 = “ba” 是xa(“dddba”) 的后缀,去掉末尾的字符a后, P1 =”b” 仍然是字符串x(“dddb”)的后缀。

由于 Pr1 是x的后缀,而k= σ (x),表示最大的k,使得 Pk 是x的后缀,那么就有 r-1 <= k 也就是 r-1 <= σ (x), 调整一下就有 r <= σ (x) + 1, 由于r = σ (xa), 于是就有:
σ (xa) <= σ (x) + 1

定理2:
对匹配字符串P, 文本字符串x, 以及任意一个字符a, 如果 q = σ (x), 那么
σ (xa) = σ ( Pq a)

先看个具体实例,P=”bacdb”, x=”ffffb”, 1 = q = σ (x), P1 =”b”,
xa = “ffffba”, 于是2 = q = σ (xa), 而 P1 a = “ba”, 从字符串P第一个字符开始,连续2个字符所构成的字符串是 P1 a的后缀,于是有 σ ( P1 a) = 2 = σ (xa).

如果 Pq 是字符串x的后缀,那么 Pq a仍然是字符串xa的后缀,如果令r= σ (xa), 也就是 Pr 是字符串xa的后缀,根据上一个定理有r <= q + 1. 由于字符串 Pr 含有r个字符, 字符串 Pq a还有q + 1个字符, r <= q + 1, 也就是字符串 Pr 的长度比字符串 Pq a要短,但是 Pq a是字符串xa的后缀,同时字符串 Pr 也是字符串xa的后缀,于是就有 Pr Pq a的后缀,因此就有r <= σ ( Pq a), 由于 σ (xa) = r, 于是 σ (xa) <= σ ( Pq a),

于此同时 Pq a又是xa的后缀,于是又成立:
σ ( Pq a) <= σ (xa)

综合两个不等式,我们有 σ ( Pq a) = σ (xa)

定理3:
给定匹配文本P, 以及要查找的文本T[1…n],那么对i=0, 1, 2…n 有:
ϕ ( Ti ) = σ ( Ti ).

也就是说,我们把字符串T[1..i]输入到自动机后,最终的状态编号k相当于匹配字符串P[1…k] 是字符串T[1..i]的后缀,如果k = m, 那么P就是T[1..i]的后缀,从而能P就包含在文本T中。

证明:
我们在i上用数学归纳法,当i=0时,结论显然成立,假设当i=k时也成立,那么有:
ϕ ( Tk ) = σ ( Tk ).
接下来我们需要证明等式 ϕ ( Tk+1 ) = σ ( Tk+1 )
令 q = ϕ ( Tk ), a = T[i+1].
ϕ ( Tk+1 ) = ϕ ( Tk a) (T[1…i+1] 相当于字符串T[1…i]后面添加字符T[i+1])

ϕ ( Tk a) = δ ( ϕ ( Tk ), a) = δ (q, a)
= σ ( Pq a)(根据状态机的构造方法) = σ ( Ti a)(根据定理2和上面的假设) = σ ( Ti+1 ).

于是我们便证明了,上一节我们所构造的状态机用于匹配字符串是正确的。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值