字符串相似度匹配算法_java逻辑表达式解析

字符串相似度匹配算法_java逻辑表达式解析本章描述了如何构造一个用于字符串匹配的有限状态自动机,依赖该自动机,可以在O(n)的时间复杂度内,判断文本T是否包含字符串P

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

阅读博客的朋友可以参看视频:
如何进入google,算法面试技能全面提升指南

什么叫有限状态自动机

先看一个图:
这里写图片描述

上面这个图描述的就叫一个有限状态自动机,图中两个圆圈,也叫节点,用于表示状态,从图中可以看成,它有两个状态,分别叫0和1. 从每个节点出发,都会有若干条边,当处于某个状态时,如果输入的字符跟该节点出发的某条边的内容一样,那么就会引起状态的转换。例如,如果当前状态处于0,输入是字符a,那么状态机就会从状态0进入状态1.如果当前状态是1,输入字符是b或a,那么,状态机就会从状态1进入状态0.如果当前所处的状态,没有出去的边可以应对输入的字符,那么状态机便会进入到错误状态。例如,如果当前处于状态0,输入字符是c,那么状态机就会出错,因为从状态0开始,没有哪条边对应的字符是c.

状态机会有一个初始节点,和一个接收节点,以上图为例,我们可以设置初始节点为0,接收节点为1,当进行一系列的输入,使得状态机的状态不断变化,只要最后一个输入使得状态机处于接收节点,那么就表明当前输入可以被状态机接收。例如对应字符串”abaaa”, 从初始节点0开始,状态机根据该字符串的输入所形成的状态变化序列为:{0,1,0,1,0,1}。由于最后状态机处于状态1,所以该字符串可以被状态机接收。如果输入的字符串是:abbaa, 那么状态机的变化序列为:{0,1,0,0,1,0}, 由于最后状态机处于非接收状态,因此这个字符串被状态机拒绝。

在程序中,我们一般使用二维表来表示一个状态机,例如上面的状态机用二维表来表示如下:

输入 a b
状态0 1 0
状态1 0 0

通过查表,我们便可知道状态机的转换,例如处于状态0,输入字符是a时,我们从表中得到的数值是1,也就是说处于状态0,输入是字符a,那么状态机将转入状态节点1.

一个文本匹配流程的描述

接下来我们看看一个文本的匹配流程,假定要查找的字符串为P=”ababaca”, 被查找的文本为T=”abababacaba”. 一次读入T的一个字符,用S表示当前读入的T的字符,一开始读入一个字符,于是S=a.然后看看,从P开始,连续几个字符所构成的字符串可以成为S的后缀,由于当前S只有一个字符a,于是从P开始,连续1个字符所形成的字符串”a”,可以作为S的后缀。把这个字符串的长度记为k,于是此时k 等于1. 继续从T中读入字符,于是S=”ab”, 此时,从P开始,连续两个字符所构成的字符串”ab”可以作为S的后缀,于是k = 2.反复这么操作,于是便有以下序列:

  1. S=a, k = 1, P[1] 是S的后缀
  2. S=ab, k = 2, P[1,2] 是S的后缀
  3. S=aba, k = 3, P[1,2,3]是S的后缀
  4. S=abab, k= 4, P[1,2,3,4]是S的后缀
  5. S=ababa, k = 5, P[1,2,3,4,5]是S的后缀
  6. S=ababab, k = 4, P[1,2,3,4]是S的后缀
  7. S=abababa, k = 5, P[1,2,3,4,5]是S的后缀
  8. S=abababac, k = 6, P[1,2,3,4,5,6]是S的后缀
  9. S=abababaca, k = 7, P[1,2,3,4,5,6,7]是S的后缀
  10. S=abababacab, k =2, P[1,2] 是S的后缀
  11. S=abababacaba, k = 3, P[1,2,3] 是S的后缀。

注意看第9步,P的长度是7,整个字符串P成为了字符串S的后缀,而此时的S是文本T的前缀,这不就表明文本T含有字符串P了吗。在每一个步骤中,我们都需要从P的第一个字符开始,看看最多能连续读取几个字符,使得他们能成为S的后缀,假设P的字符个数为m, 那么这个读取过程最多需要读取m个字符,于是复杂度为O(m). 如果有某种办法,使得我们一次就可以知道从P开始,连续读取几个字符就可以构成S 的后缀,假设文本T含有n个字符,那么我们就可以在O(n)的时间内判断,T是否含有字符串P.因为上面的步骤最多可以执行n次。

于是当前问题变成,构造一个方法,使得一次运行便能知道从P开始,连续读取几个字符能使,得这几个字符构成的字符串是S的后缀。这个方法,就需要上面我们提到的有限状态自动机。

用于字符串匹配的自动机

假定字符串P和文本T只由a,b两个字符组成,也就是字符集为 ={a,b,c}, P含有m个字母,于是,我们要构造的自动机就含有m个状态节点。假设我们当前处于状态节点q, 那么当下一个输入字符是a和b时,从当前节点q该跳转到哪一个节点呢? 如果用 Pq 来表示长度为q的P的前缀,以q=4, p=”ababaca”, Pq =”abab”, 那么当处于状态4, 当输入为a时,我们构造字符串 S = Pq + ‘a’ = “ababa”, 然后看看字符串P从第一个字符开始,连续几个字符所构成的字符串可以成为S的后缀,就当前S为例,从第一个字符开始,连续5个字符,也就是P[1,2,3,4,5]可以作为S的后缀,于是,我们就有,当状态机处于节点4,输入为a时,跳转的下个状态就是5. 同理,当处于状态q=4,输入为字符b时,S = Pq + ‘b’ = “ababb”,此时从P开始,连续读取0个字符才能形成S的后缀,于是当状态机处于状态4,如果读入字符是b, 那么跳转的下一个状态是0,同理,如果输入字符是c, 那么S = Pq + ‘c’ = “ababc”, 此时从P开始,连续读取0个字符所形成的空字符串才能作为S的后缀,于是当状态机处于状态节点4,输入字符为c时,跳转到节点0. 如果q从0开始,一直到m,反复运用刚才提到的步骤,便会产生下面这个跳转表:

输入 a b c
状态0 1 0 0
状态1 1 2 0
状态2 3 0 0
状态3 1 4 0
状态4 5 0 0
状态5 1 4 6
状态6 7 0 0
状态7 1 2 0

利用上面的状态机,依次读入T的字符,如果状态机跳转到状态q,那就表明从P的第一个字符开始,连续读取q个字符,所形成的字符串可以构成是S的后缀,也就是说,当我们的状态机跳转到状态7时,我们就可以得知文本T,包含字符串P.

我们走一遍这个过程,首先状态机处于状态0,读入T[0]=a, S= a, 查表可知进入状态1,读入T[1]=b, S=ab, 查表可知,进入状态2,读入T[2]=a,查表可知进入状态3,读入T[3]=b, S=abab,查表可知进入状态4,读入T[4]=a,S=ababa,查表可知进入状态5,读入T[5]=b,S=ababab,查表可知进入状态4,读入T[6]=a, S=abababa,查表可知进入状态5,读入T[7]=c,S=abababac,查表可知进入状态6,读入T[8]=a,S=abababaca,查表可知进入状态7,此时,我们可以得出结论,文本T包含有字符串P.

代码实现

import java.util.HashMap;


public class StringAutomaton { 
   
   private HashMap<Integer, HashMap<Character, Integer>> jumpTable = new HashMap<Integer, HashMap<Character, Integer>>();
   String P = "";
   private final int alphaSize = 3;
   public StringAutomaton(String p) {
       this.P = p;
       makeJumpTable();
   }

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

   private int findSuffix(String Pq) {
       int suffixLen = 0;
       int k = 0;

       while(k < Pq.length() && k < P.length()) {
           int i = 0;
           for (i = 0; i <= k; i++) {
               if (Pq.charAt(Pq.length() - 1 - k + i) != P.charAt(i)) {
                   break;
               }
           }

           if (i - 1 == k) {
              suffixLen = k+1;
           } 

           k++;
       }

       return suffixLen;
   }

   public int match(String T) {
       Integer q = 0;
       System.out.println("Begin matching...");

       for (int n = 0; n <= T.length(); n++) {
           HashMap<Character, Integer> map = jumpTable.get(q);
           int oldState = q;
           q = map.get(T.charAt(n));
           if (q == null) {
               return -1;
           }

           System.out.println("In state " + oldState + " receive input " + T.charAt(n) + " jump to state " + q);

           if (q == P.length()) {
               return q;
           }
       }

       return -1;
   }
}

代码中,makeJumpTable调用用来构建跳转表,findSuffix用来查找最大的数值K, 使得P[1…k] 是字符串 Pq 的后缀,该调用有两层循环,所以复杂度是O( m2 ), makeJumpTable有两层循环,循环次数为O(m*| |), 所以makeJumpTable总的时间复杂度为O( m3 | |), 也就是说,构建跳转表的复杂度是:O( m3 | |)。

match依靠跳转表来判断,输入的字符串T是否包含字符串P,如果T的最后一个字符输入状态机后,从跳转表得到的状态的值等于P的长度m,那么表明T包含字符串P.具体的程序调试过程请参看视频。

我们只给出了算法的实现流程,算法的数学原理比较复杂,我们将在下一节详解。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/171700.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月21日 下午2:46
下一篇 2022年8月21日 下午2:46


相关推荐

  • navicat15.0.25激活码【2021最新】

    (navicat15.0.25激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.htmlS32PGH0SQB-eyJsaWN…

    2022年3月25日
    116
  • Django(19)QuerySet API[通俗易懂]

    Django(19)QuerySet API[通俗易懂]前言我们通常做查询操作的时候,都是通过模型名字.objects的方式进行操作。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager

    2022年7月31日
    6
  • 火山大模型使用教程

    火山大模型使用教程

    2026年3月12日
    2
  • git的版本回退教程(带你一步一步操作)

    git的版本回退教程(带你一步一步操作)nbsp nbsp 在之前的文章中我们已经学会了如何使用 git 提交文件 下载更新文件 那么在 git 中如何进行版本回退呐 nbsp 首先 在本地建立一个 git 项目 并且与远程服务端 github 上的项目进行关联 如果这一步骤有问题的童靴 请参考我的上一篇文章 害羞 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 1 第一次建立 git 项目 提交到远程分支 并且记录为第一个版本 nbsp nbsp

    2026年3月18日
    1
  • 深度学习 arm linux移植过程整理[通俗易懂]

    深度学习 arm linux移植过程整理[通俗易懂]一、环境搭建下载虚拟机VMwareWorkstation自行下载激活成功教程下载ubtun因运行环境使用ubtun18所虚拟机下载的ubtun18下载比较慢的话可以更换国内镜像https://cn.ubuntu.com/download/server/step1vm中安装ubtun虚拟机https://zhuanlan.zhihu.com/p/141033713下载支持包编译服务器需要安装包makecmake交叉编译链arm-linux-gunebhf例如:ap

    2026年3月7日
    4
  • SQL Server2012新特性概述

    SQL Server2012新特性概述

    2021年11月24日
    57

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号