字符串匹配算法

推荐阅读:(程序员小灰)

漫画:什么是字符串匹配算法

总述

(m为主串的长度,n为子串的长度)

  1. 暴力的BF Brute Force暴力算法,直接单个比较,时间复杂度为O(m*n),java底层API中的indexOf就是BF算法直接实现的
  2. RK算法(Rabin-Karp两位发明者) 利用hash进行比较后再字符串比较。时间复杂度:O(n)
  3. BM算法,采用 “坏字符”+“好后缀”进行匹配,每次进行跳跃匹配,以此避免BF中单个字符移动进行无效匹配的次数。从右往左找“坏字符”;在前缀中找好后缀
  4. KMP算法,详见:KMP算法 字符串匹配

RK算法

简单介绍一下RK算法:
hash的方式有很多,我们要在“出现hash冲突”以及“计算hash的复杂度”之间权衡以后进行合适的选择。
这里我们采用a为1,b为2。。的**“按位相加”**的hash方式。

还有一种常见的hash方式:
转换成26进制数
既然字符串只包含26个小写字母,那么我们可以把每一个字符串当成一个26进制数来计算。
这样做的好处是大幅减少了hash冲突,缺点是计算量较大,而且有可能出现超出整型范围的情况,需要对计算结果进行取模。

bce = 2*(26^2) + 3*26 + 5 = 1435

我们采用“按位相加”的方式进行介绍

  1. 先将目标子串(模式串pattern)的hash计算出来:targethashCode
  2. 将主串中和pattern长度相等的子串的hash分别计算出来,注意,当有了第一个子串的hashCode1之后,下一个子串的hashCode2就=hashCode1-hash(左侧字符)+hash(右侧字符)。
  3. 当hashCode和targethashCode相等的时候,进行equals的比较即可

所以 每次子串和模式串之间的对比都是O1的复杂度,计算hash就是On时间复杂度,整体就是On
注意点:其实很容易产生冲突,如果冲突过多,就相当于每次都要equals,时间复杂度就退化到了BF算法的复杂度:O(m*n)。

BM算法

介绍查看:BM算法漫画介绍

BM算法的要点:“坏字符”+“好后缀”。

坏字符含义:
在这里插入图片描述

  1. 如果出现了“坏字符”,就找未匹配的模式串左侧最靠右的首个和“坏字符”相等的字符,然后挪过去在这里插入图片描述
  2. 如果只是依赖“坏字符”原则,其实已经可以减少很多无效匹配了。但如果出现下图所示的情况,每次还是只移动一位,所以我们接下来可以再进行一个“好后缀”的原则匹配。
    在这里插入图片描述
    “好后缀”概念:主串的子串和模式串中都有共同的后缀“GCG”,这个就是好后缀。
    如果此时发现模式串的其他位置anotherPlace还有好后缀,那么就可以挪动模式串,直接将anotherPlace和主串的好后缀对齐,就可减少比较次数
    在这里插入图片描述
    假如模式串中不存在anotherPlace,那么就在模式串的前缀中找是否有和“好后缀的后缀”相等的字符串,然后进行移动(这个就比较像KMP算法了)在这里插入图片描述
    现在我们已经知道什么是“坏字符”,什么是”好前缀“。那么我们怎么判断什么时候用坏字符规则,什么时候用好前缀规则呢?
    答案很简单,直接每一轮分别按照两种规则计算相应的挪动距离,哪个距离长,就挪动相应的长度。

附上RK算法的代码和BM算法的简单版本

// RK算法

    public static int rabinKarp(String str,String pattern) {

//主串长度

        int m = str.length();

//模式串的长度

        int n = pattern.length();

//计算模式串的hash值

        int patternCode = hash(pattern);

//计算主串当中第一个和模式串等长的子串hash值

        int strCode = hash(str.substring(0, n));

//用模式串的hash值和主串的局部hash值比较。

//如果匹配,则进行精确比较;如果不匹配,计算主串中相邻子串的hash值。

        for(int i = 0; i < m - n + 1; i++) {
            if (strCode == patternCode && compareString(i, str, pattern)) {
                return i;
            }

//如果不是最后一轮,更新主串从i到i+n的hash值

            if (i < m - n) {
                strCode = nextHash(str, strCode, i, n);
            }
        }
        return -1;
    }

    private static int hash(String str) {

        int hashcode =0;

//这里采用最简单的hashcode计算方式:

//把a当做1,把b当中2,把c当中3.....然后按位相加

        for(int i =0; i < str.length(); i++) {
            hashcode += str.charAt(i) - 'a';
        }

        return hashcode;
    }

    private static int nextHash(String str,int hash,int index,int n) {
        hash -= str.charAt(index) - 'a';
        hash += str.charAt(index + n) - 'a';

        return hash;
    }

    private static boolean compareString(int i,String str,String pattern) {

        String strSub = str.substring(i, i + pattern.length());

        return strSub.equals(pattern);
    }

	public static void main(String[] args) {

        String str = "aacdesadsdfer";

        String pattern = "adsd";

        System.out.println("第一次出现的位置:" + rabinKarp(str, pattern));
    }
 //在模式串中,查找index下标之前的字符是否和坏字符匹配
    private static int findCharacter(String pattern, char badCharacter, int index) {

        for (int i = index - 1; i >= 0; i--) {
            if (pattern.charAt(i) == badCharacter) {
                return i;
            }
        }

//模式串不存在该字符,返回-1

        return -1;
    }


    public static int boyerMoore(String str, String pattern) {

        int strLength = str.length();

        int patternLength = pattern.length();

//模式串的起始位置

        int start = 0;

        while (start <= strLength - patternLength) {
            int i;
            //从后向前,逐个字符比较
            for (i = patternLength - 1; i >= 0; i--) {
                if (str.charAt(start + i) != pattern.charAt(i))
//发现坏字符,跳出比较,i记录了坏字符的位置
                    break;
            }

            if (i < 0) {
//匹配成功,返回第一次匹配的下标位置
                return start;

            }
//寻找坏字符在模式串中的对应
            int charIndex = findCharacter(pattern, str.charAt(start + i), i);
//计算坏字符产生的位移
            int bcOffset = charIndex >= 0 ? i - charIndex : i + 1;
            start += bcOffset;

        }

        return -1;
    }


    public static void main(String[] args) {

        String str = "GTTATAGCTGGTAGCGGCGAA";

        String pattern = "GTAGCGGCG";

        int index = boyerMoore(str, pattern);

        System.out.println("首次出现位置:" + index);
    }
  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值