BF算法:编程语言中的查找、替换函数是怎么实现的

文章内容转载 王争的《数据结构与算法之美》


1.BF算法的原理与实现

        为了方便之后的讲解,我们先来看两个概念:主串和模式串。这两个概念在所有的字符串匹配算法中都会用到。如果在字符串a中查找字符串b,那么字符串a就是主串,字符串b就是模式串。我们把主串的长度记作n,模式串的长度记作m。在一般情况下,n大于或等于m。虽然这不是必须得,但如果n小于m,那么在主串肯定不存在模式串。

        BF(Brute Force, 暴力匹配) 算法也称为朴素匹配算法。从名字可以看出,这种匹配方式很“暴力”,简单直接,性能不高。

        作为简单和“暴力”的字符串匹配算法,BF算法的思想可以用一句话来概括:如果模式串的长度为m,主串的长度为n,那么在主串中就会有一个n-m+1个长度为m的子串,我们只需要“暴力”地对比这n-m+1个子串与模式串,就可以找出主串与模式串匹配的子串。当然,在具体处理的时候,我们并非把n-m+1个子串都事先罗列出来,而是通过下标操作,让起始下标分别为0,1,2,...,n-m的子串与模式串尝试匹配。BF算法示例如图所示。

        基于上述原理,BF算法的代码实现如下所示。

//返回第一个匹配的起始下标位置
int bf(char a[], int n, char b[], int m) {
    for(int i = 0; i < n-m; i++){
        int j = 0;
        while (j < m){
            if(a[i+j] != b[j]){
                break;    
            }
            j++;
        }
        if( j== m){
            return i;
        }
    }
    return -1;
}

2.BF算法的性能分析

        在大部分情况下,时间复杂度表示为数据规模n这一个变量的表达式,但是,在有些情况下,时间复杂度会表示为两个变量的表达式,如O(nm)、O(n+m)。对于字符串匹配算法,其时间复杂度的表示,需要主串数据规模n和模式串数据规模m的共同参与。

        从BF算法的原理和代码实现,我们可以看出,在极端情况下,如主串是“aaaa...aaaa”(省略号表示有很多重复的字符a),模式串是“aaaaab”。我们用主串中的n-m+1个子串与模式串匹配,每个子串与模式串都需要对比m个字符,这样才能发现无法匹配,因此,最坏时间复杂度是O(nm)。

        从理论上来讲,尽管BF算法的时间复杂度很高,但在实际的开发中,它却是一个比较常用的字符串匹配算法,原因有下面3点。

        第一,在实际的软件开发中,大部分情况下,模式串与主串的长度都不会太长。对于小规模数据的处理,时间复杂度的高低并不能代表代码真正的执行时间,有些情况下,时间复杂度高的算法可能比时间复杂度低的算法的运行速度更快。

        第二,当每次模式串与主串中的子串匹配的时候,如果中途遇到不能匹配的字符,就可以提前终止,不需要把m个字符都对比一遍。因此,尽管理论上最坏时间复杂度是O(nm),但是,从概率统计上来看,大部分情况下,算法的执行效率要比最坏情况下的执行效率高很多。

        第三,BF算法的思想简单,代码实现也非常简单。简单意味着不容易出错,即使存在bug也容易暴露和修复。在工程中,在满足性能要求的前提下,简单是我们的首选。这也符合我们常说的KISS(Keep It Simple and Stupid)设计原则。

        因此,在实际的软件开发中,绝大部分情况下,朴素的字符串匹配算法就够用了。

3.内容小结

        BF算法的实现思路非常简单,用模式串与主串中所有长度为m的子串进行匹配,其性能与更加高效的KMP、BM算法可能相差无几,甚至因为逻辑简单而更加高效,所以,在实际的软件开发中,BF算法较为常用,另外,大部分的编程语言中提供了相对应的函数实现。

4.思考

        字符串匹配算法返回的结果是第一个匹配子串的首地址,如果要返回说有匹配子串的首地址,该如何实现呢?如何实现替换函数replace()?

    int matchedPos[] = new int[n];//申请大一点的空间
    int bf(char mainStr[], int n,char subStr[], int m,int matchedPos[]){
        int matchedNum = 0;
        for (int i = 0; i < n - m; i++) {
            int j = 0;
            while (j < m){
                if (mainStr[i+j] != subStr[j]){
                    break;
                }
                j++;
            }
            if (j == m){
                matchedPos[matchedNum] = i;
                matchedNum++;
            }
        }
        return matchedNum;
    }

char* replace(char mStr[], int n, char pStr[], int m, char[] rStr, int k){
        int[] matchedPos = new int[n];
        int matchedNum = bf(mStr, n, pStr, m, matchedPos);
        char[] newStr = new char[n + (k-m)*matchedNum];
        int p = 0;//mStr上的游标
        int q = 0;//newStr上的游标
        for (int i = 0; i < matchedNum; i++) {
            while (p < matchedPos[i]){
                newStr[q++] = mStr[p++];
            }
            for (int j = 0; j < k; j++) {
                newStr[q++] = rStr[j];]
            }
            p+=m;
        }
        while (p<n){
            newStr[q++] = mStr[p++];
        }
        return newStr;
    }

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值