今日了解了一下字符串匹配的各种方法。

  并对sundaysearch算法实现并且单元。

字符串匹配算法,是在实际工程中经常遇到的问题,也是各大公司笔试面试的常考题目。此算法通常输入为原字符串(string)和子串(pattern),要求返回子串在原字符串中首次出现的位置。比如原字符串为“ABCDEFG”,子串为“DEF”,则算法返回3。常见的算法包括:BF(Brute Force,暴力检索)、RK(Robin-Karp,哈希检索)、KMP(教科书上最常见算法)、BM(Boyer Moore)、Sunday等,下面详细介绍。

1 BF算法: 
暴力检索法是最好想到的算法,也最好实现,在情况简单的情况下可以直接使用: 

首先将原字符串和子串左端对齐,逐一比较;如果第一个字符不能匹配,则子串向后移动一位继续比较;如果第一个字符匹配,则继续比较后续字符,直至全部匹配。 
时间复杂度:O(MN)

2 RK算法: 
RK算法是对BF算法的一个改进:在BF算法中,每一个字符都需要进行比较,并且当我们发现首字符匹配时仍然需要比较剩余的所有字符。而在RK算法中,就尝试只进行一次比较来判定两者是否相等。 
RK算法也可以进行多模式匹配,在论文查重等实际应用中一般都是使用此算法。 
 
首先计算子串的HASH值,之后分别取原字符串中子串长度的字符串计算HASH值,比较两者是否相等:如果HASH值不同,则两者必定不匹配,如果相同,由于哈希冲突存在,也需要按照BF算法再次判定。 
按照此例子,首先计算子串“DEF”HASH值为Hd,之后从原字符串中依次取长度为3的字符串“ABC”、“BCD”、“CDE”、“DEF”计算HASH值,分别为Ha、Hb、Hc、Hd,当Hd相等时,仍然要比较一次子串“DEF”和原字符串“DEF”是否一致。 
时间复杂度:O(MN)(实际应用中往往较快,期望时间为O(M+N))

3 KMP算法: 
字符串匹配最经典算法之一,各大教科书上的看家绝学,曾被投票选为当今世界最伟大的十大算法之一;但是晦涩难懂,并且十分难以实现,希望我下面的讲解能让你理解这个算法。 
KMP算法在开始的时候,也是将原字符串和子串左端对齐,逐一比较,但是当出现不匹配的字符时,KMP算法不是向BF算法那样向后移动一位,而是按照事先计算好的“部分匹配表”中记载的位数来移动,节省了大量时间。这里我借用一下阮一峰大神的例子来讲解: 
 
首先,原字符串和子串左端对齐,比较第一个字符,发现不相等,子串向后移动,直到子串的第一个字符能和原字符串匹配。 
 
当A匹配上之后,接着匹配后续的字符,直至原字符串和子串出现不相等的字符为止。 
 
此时如果按照BF算法计算,是将子串整体向后移动一位接着从头比较;按照KMP算法的思想,既然已经比较过了“ABCDAB”,就要利用这个信息;所以针对子串,计算出了“部分匹配表”如下(具体如何计算后面会说,这个先介绍整个流程): 
 
刚才已经匹配的位数为6,最后一个匹配的字符为“B”,查表得知“B”对应的部分匹配值为2,那么移动的位数按照如下公式计算: 
移动位数 = 已匹配的位数 - 最后一个匹配字符的部分匹配值 
那么6 - 2 = 4,子串向后移动4位,到下面这张图: 
 
因为空格和“C”不匹配,已匹配位数为2,“B”对应部分匹配值为0,所以子串向后移动2-0=2位。 
 
空格和“A”不匹配,已匹配位数为0,子串向后移动一位。 
 
逐个比较,直到发现“C”与“D”不匹配,已匹配位数为6,“B”对应部分匹配值为2,6-2=4,子串向后移动4位。 
 
逐个比较,直到全部匹配,返回结果。 
下面说明一下“部分匹配表”如何计算,“部分匹配值”是指字符串前缀和后缀所共有元素的长度。前缀是指除最后一个字符外,一个字符串全部头部组合;后缀是指除第一个字符外,一个字符串全部尾部组合。以”ABCDABD”为例: 
“AB”的前缀为[A],后缀为[B],共有元素的长度为0; 
“ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0; 
“ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0; 
“ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1; 
“ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2; 
“ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。 
在计算“部分匹配表”时,一般使用DP(动态规划)算法来计算(表示为next数组):

        int* next = new int[needle.length()];
next[0] = 0;
int k = 0;
for (int i = 1; i < needle.length(); i++)
{
while (k > 0 && needle[i] != needle[k])
{
k = next[k - 1];
}
if (needle[i] == needle[k])
{
k++;
}
next[i] = k;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

时间复杂度:O(N)

4 BM算法: 
在本科的时候,我一直认为KMP算法是最好的字符串匹配算法,直到后来我遇到了BM算法。BM算法的执行效率要比KMP算法快3-5倍左右,并且十分容易理解。各种记事本的“查找”功能(CTRL + F)一般都是采用的此算法。 
网上所有讲述这个算法的帖子都是以传统的“好字符规则”和“坏字符规则”来讲述的,但是个人感觉其实这样不容易理解,我总结了另外一套简单的算法规则: 
我们拿这个算法的发明人Moore教授的例子来讲解: 
 
首先,原字符串和子串左端对齐,但是从尾部开始比较,就是首先比较“S”和“E”,这是一个十分巧妙的做法,如果字符串不匹配的话,只需要这一次比较就可以确定。 
在BM算法中,当每次发现当前字符不匹配的时候,我们就需要寻找一下子串中是否有这个字符;比如当前“S”和“E”不匹配,那我们需要寻找一下子串当中是否存在“S”。发现子串当中并不存在,那我们将子串整体向后移动到原字符串中“S”的下一个位置(但是如果子串中存在原字符串当前字符肿么办呢,我们后面再说): 
 
我们接着从尾部开始比较,发现“P”和“E”不匹配,那我们查找一下子串当中是否存在“P”,发现存在,那我们就把子串移动到两个“P”对齐的位置: 
 
已然从尾部开始比较,“E”匹配,“L”匹配,“P”匹配,“M”匹配,“I”和“A”不匹配!那我们就接着寻找一下子串当前是否出现了原字符串中的字符,我们发现子串中第一个“E”和原字符串中的字符可以对应,那直接将子串移动到两个“E”对应的位置: 
 
接着从尾部比较,发现“P”和“E”不匹配,那么检查一下子串当中是否出现了“P”,发现存在,那么移动子串到两个“P”对应: 
 
从尾部开始,逐个匹配,发现全部能匹配上,匹配成功~ 
时间复杂度:最差情况O(MN),最好情况O(N)

5 Sunday算法: 
后来,我又发现了一种比BM算法还要快,而且更容易理解的算法,就是这个Sunday算法: 
 
首先原字符串和子串左端对齐,发现“T”与“E”不匹配之后,检测原字符串中下一个字符(在这个例子中是“IS”后面的那个空格)是否在子串中出现,如果出现移动子串将两者对齐,如果没有出现则直接将子串移动到下一个位置。这里空格没有在子串中出现,移动子串到空格的下一个位置“A”: 
 
发现“A”与“E”不匹配,但是原字符串中下一个字符“E”在子串中出现了,第一个字符和最后一个字符都有出现,那么首先移动子串靠后的字符与原字符串对齐: 
 
发现空格和“E”不匹配,原字符串中下一个字符“空格”也没有在子串中出现,所以直接移动子串到空格的下一个字符“E”: 
 
这样从头开始逐个匹配,匹配成功! 
时间复杂度:最差情况O(MN),最好情况O(N)

sunday算法代码如下:

 import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SundySearch {
    String text = null;
    String pattern = null;
    int currentPos = 0;
    /**
     * 匹配后的子串第一个字符位置列表
     */
    List<Integer> matchedPosList = new LinkedList<Integer>();

/**
     * 匹配字符的Map,记录改匹配字符串有哪些char并且每个char最后出现的位移
     */
    Map<Character, Integer> map = new HashMap<Character, Integer>();
    public SundySearch(String text, String pattern) {
        this.text = text;
        this.pattern = pattern;
        this.initMap();
    };
    /**
     * Sunday匹配时,用来存储Pattern中每个字符最后一次出现的位置,从左到右的顺序
     */
    private void initMap() {
        for (int i = 0; i < pattern.length(); i++) {
            this.map.put(pattern.charAt(i), i);
        }
    }
    /**
     * 普通的字符串递归匹配,匹配失败就前进一位
     */
    public List<Integer> normalMatch() {
        //匹配失败,继续往下走
        if (!matchFromSpecialPos(currentPos)) {
            currentPos += 1;
            if ((text.length() - currentPos) < pattern.length()) {
                return matchedPosList;
            }
            normalMatch();
        } else {
            //匹配成功,记录位置
            matchedPosList.add(currentPos);
            currentPos += 1;
            normalMatch();
        }

return matchedPosList;
    }
    /**
     * Sunday匹配,假定Text中的K字符的位置为:当前偏移量+Pattern字符串长度+1
     */
    public List<Integer> sundayMatch() {
        // 如果没有匹配成功
        if (!matchFromSpecialPos(currentPos)) {
            // 如果Text中K字符没有在Pattern字符串中出现,则跳过整个Pattern字符串长度
            if ((currentPos + pattern.length() + 1) < text.length()
                    && !map.containsKey(text.charAt(currentPos + pattern.length() + 1))) {
                currentPos += pattern.length();
            }else {
                // 如果Text中K字符在Pattern字符串中出现,则将Text中K字符的位置和Pattern字符串中的最后一次出现K字符的位置对齐
                if ((currentPos + pattern.length() + 1) > text.length()) {
                    currentPos += 1;
                } else {
                    currentPos += pattern.length() - (Integer) map.get(text.charAt(currentPos + pattern.length()));
                }
            }

// 匹配完成,返回全部匹配成功的初始位移
            if ((text.length() - currentPos) < pattern.length()) {
                return matchedPosList;
            }

sundayMatch();
        }else {
            // 匹配成功前进一位然后再次匹配
            matchedPosList.add(currentPos);
            currentPos += 1;
            sundayMatch();
        }
        return matchedPosList;
    }
    /**
     * 检查从Text的指定偏移量开始的子串是否和Pattern匹配
     */
    public boolean matchFromSpecialPos(int pos) {
        if ((text.length()-pos) < pattern.length()) {
            return false;
        }
        for (int i = 0; i < pattern.length(); i++) {
            if (text.charAt(pos + i) == pattern.charAt(i)) {
                if (i == (pattern.length()-1)) {
                    return true;
                }
                continue;
            } else {
                break;
            }
        }

return false;
    }
  
}

 
测试sunday算法代码如下:
 import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SundySearch {
    String text = null;
    String pattern = null;
    int currentPos = 0;
    /**
     * 匹配后的子串第一个字符位置列表
     */
    List<Integer> matchedPosList = new LinkedList<Integer>();

/**
     * 匹配字符的Map,记录改匹配字符串有哪些char并且每个char最后出现的位移
     */
    Map<Character, Integer> map = new HashMap<Character, Integer>();
    public SundySearch(String text, String pattern) {
        this.text = text;
        this.pattern = pattern;
        this.initMap();
    };
    /**
     * Sunday匹配时,用来存储Pattern中每个字符最后一次出现的位置,从左到右的顺序
     */
    private void initMap() {
        for (int i = 0; i < pattern.length(); i++) {
            this.map.put(pattern.charAt(i), i);
        }
    }
    /**
     * 普通的字符串递归匹配,匹配失败就前进一位
     */
    public List<Integer> normalMatch() {
        //匹配失败,继续往下走
        if (!matchFromSpecialPos(currentPos)) {
            currentPos += 1;
            if ((text.length() - currentPos) < pattern.length()) {
                return matchedPosList;
            }
            normalMatch();
        } else {
            //匹配成功,记录位置
            matchedPosList.add(currentPos);
            currentPos += 1;
            normalMatch();
        }

return matchedPosList;
    }
    /**
     * Sunday匹配,假定Text中的K字符的位置为:当前偏移量+Pattern字符串长度+1
     */
    public List<Integer> sundayMatch() {
        // 如果没有匹配成功
        if (!matchFromSpecialPos(currentPos)) {
            // 如果Text中K字符没有在Pattern字符串中出现,则跳过整个Pattern字符串长度
            if ((currentPos + pattern.length() + 1) < text.length()
                    && !map.containsKey(text.charAt(currentPos + pattern.length() + 1))) {
                currentPos += pattern.length();
            }else {
                // 如果Text中K字符在Pattern字符串中出现,则将Text中K字符的位置和Pattern字符串中的最后一次出现K字符的位置对齐
                if ((currentPos + pattern.length() + 1) > text.length()) {
                    currentPos += 1;
                } else {
                    currentPos += pattern.length() - (Integer) map.get(text.charAt(currentPos + pattern.length()));
                }
            }

// 匹配完成,返回全部匹配成功的初始位移
            if ((text.length() - currentPos) < pattern.length()) {
                return matchedPosList;
            }

sundayMatch();
        }else {
            // 匹配成功前进一位然后再次匹配
            matchedPosList.add(currentPos);
            currentPos += 1;
            sundayMatch();
        }
        return matchedPosList;
    }
    /**
     * 检查从Text的指定偏移量开始的子串是否和Pattern匹配
     */
    public boolean matchFromSpecialPos(int pos) {
        if ((text.length()-pos) < pattern.length()) {
            return false;
        }
        for (int i = 0; i < pattern.length(); i++) {
            if (text.charAt(pos + i) == pattern.charAt(i)) {
                if (i == (pattern.length()-1)) {
                    return true;
                }
                continue;
            } else {
                break;
            }
        }

return false;
    }
  
}

 
 

字符串匹配常见算法(BF,RK,KMP,BM,Sunday)的更多相关文章

  1. 字符串匹配Boyer-Moore算法:文本编辑器中的查找功能是如何实现的?---这应该讲的最容易懂的文章了!

    关于字符串匹配算法有很多,之前我有讲过一篇 KMP 匹配算法:图解字符串匹配 KMP 算法,不懂 kmp 的建议看下,写的还不错,这个算法虽然很牛逼,但在实际中用的并不是特别多.至于选择哪一种字符串匹 ...

  2. 字符串匹配&Rabin-Karp算法讲解

    问题描述: Rabin-Karp的预处理时间是O(m),匹配时间O( ( n - m + 1 ) m )既然与朴素算法的匹配时间一样,而且还多了一些预处理时间,那为什么我们还要学习这个算法呢?虽然Ra ...

  3. 字符串匹配--Karp-Rabin算法

    主要特征 1.使用hash函数 2.预处理阶段时间复杂度O(m),常量空间 3.查找阶段时间复杂度O(mn) 4.期望运行时间:O(n+m) 本文地址:http://www.cnblogs.com/a ...

  4. 字符串匹配(hash算法)

    hash函数对大家来说不陌生吧 ? 而这次我们就用hash函数来实现字符串匹配. 首先我们会想一下二进制数. 对于任意一个二进制数,我们将它化为10进制的数的方法如下(以二进制数1101101为例): ...

  5. leetcode28 strstr kmp bm sunday

    字符串匹配有KMP,BM,SUNDAY算法. 可见(https://leetcode-cn.com/problems/implement-strstr/solution/c5chong-jie-fa- ...

  6. 字符串匹配 扩展KMP BM&Sunday

    复杂度都是O(n) 扩展1:BM算法 KMP的匹配是从模式串的开头开始匹配的,而1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹 ...

  7. 【数据结构与算法】字符串匹配(Rabin-Karp 算法和KMP 算法)

    Rabin-Karp 算法 概念 用于在 一个字符串 中查找 另外一个字符串 出现的位置. 与暴力法不同,基本原理就是比较字符串的 哈希码 ( HashCode ) , 快速的确定子字符串是否等于被查 ...

  8. 字符串匹配问题(暴力,kmp)

    对于字符串的匹配问题,现在自己能够掌握的就只有两种方法, 第一种就是我们常用的暴力匹配法,那什么是暴力匹配法呢? 假设我们现在有一个文本串和一个模式串,我们现在要找出模式串在文本串的哪个位置. 文本串 ...

  9. 字符串匹配--manacher算法模板

    manacher算法主要是处理字符串中关于回文串的问题的,它可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径,由于将原字符串处理成两倍长度的新串,在每两个字符之间加入一个特定的特殊 ...

随机推荐

  1. MVC3学习:利用mvc3+ajax结合MVCPager实现分页

    本例使用表格Users(Uid,UserName,PassWord),数据库访问使用EF first code. public class Users { [Key] public int Uid { ...

  2. 公共技术点(Android 动画基础)

    转载地址:http://p.codekk.com/blogs/detail/559623d8d6459ae793499787 一 传统 View 动画(Tween/Frame) 1.1 Tween 动 ...

  3. Spring中内置的一些工具类

    学习Java的人,或者开发很多项目,都需要使用到Spring 这个框架,这个框架对于java程序员来说.学好spring 就不怕找不到工作.我们时常会写一些工具类,但是有些时候 我们不清楚,我们些的工 ...

  4. 堆与堆排序/Heap&Heap sort

    最近在自学算法导论,看到堆排序这一章,来做一下笔记.堆排序是一种时间复杂度为O(lgn)的原址排序算法.它使用了一种叫做堆的数据结构.堆排序具有空间原址性,即指任何时候都需要常数个额外的元素空间存储临 ...

  5. Java 范例 - 线程

    创建线程 Java 中有以下三种方式创建线程,其中前两种无法获取返回值,而最后一种可以获取返回值. 实现 Runnable 接口 继承 Thread 类 通过 Callable.Future 接口配合 ...

  6. Windows下编译打包Spice PC客户端

    目录 1 环境搭建 2 编译客户端 3 打包客户端   1 环境搭建 1.1 准备工作 安装启动: 安装替换图标工具: Resource Hacker 安装exe制作工具: NSIS(提取码:3dfp ...

  7. linux firefox 快捷方式

    .输入:cd /usr/share/applications .输入:vi firefox.desktop 在vi里面输入以下内容,然后保存并退出: [Desktop Entry] Name=Fire ...

  8. 基于Hadoop2.6.5(HA)的HBase2.0.5配置

    1.配置 在CentOS7Three上配置,注意:一定要安装bin包,不能安装src包 /usr/local/hbase/hbase-2.0.5/conf 编辑hbase-env.sh,替换成如下配置 ...

  9. PowerBuilder编程新思维2:嵌入(Thread多线程)

    PowerBuilder编程新思维2:嵌入(Thread多线程) 在PB中使用多线程,在网上有大量的文章介绍.不过深入研究并试着给出更易用的模型的,目前还只有"路人甲cw"的一篇& ...

  10. gulpjs

    http://www.cnblogs.com/2050/p/4198792.html   这篇文章很全面的讲解了gulpjs的使用 https://www.jianshu.com/p/9723ca2a ...