美文网首页On lzq ways
字符串匹配算法总结

字符串匹配算法总结

作者: zhengqiuliu | 来源:发表于2019-05-30 19:19 被阅读8次

    面写过一篇字符串匹配算法,总共涉及BF算法,RK算法,BM算法,还有一种算法是KMP算法。这几种算法思想和代码我都认真阅读完之后,发现BM算法和KMP算法还是很难完全掌握。如果自己闭卷再做一次这两种算法,我心里还是没有底气。

    这两种算法思想都比较复杂,没有简单的数学公式可以依赖,场景较多,并且代码实现较为巧妙,很难用代码直观的表达算法思想。

    假设主串A=ggusfsabxabcabxabxyyy, 长度为n 模式串B=abxabcabxabx 长度为m

    BF算法:对A进行遍历,截取12长度的子串,子串和B进行逐个字符比较,如果完全相同则找到。算法思想简单直观,容易理解。但是如果比较的字符串长度较长时,这种算法的效率较低,时间复杂度=O(m * (n - m)) = O(m * n)

    RK算法:和BF算法思想类似,需要提前设计好一个散列函数,把B转换为整数,然后把A中截取的子串也转换为整数,这样子串和B就可以直接通过整数进行比较,时间复杂度立刻降为O(n-m)。但是要设计一个好的散列函数,匹配各种类型是比较困难的。

    下面再介绍两种重量级的算法BM和KMP

    两种算法都是在比较子串时,不用一个一个字符移动比较,而是移动较大的位置,提高比较的效率。

    BM算法

    坏字符规则:模式串B与主串的子串C,B从后往前逐字符与C比较,如果遇到不相等的字符则记为坏字符。遇到坏字符处理方式:B继续从后往前对比寻找最近与坏字符相等的字符,如果找到则移动到对应位置;如果没有找到,则跨越整个模式串。从后往前找到相同字符,还分两种情况找到的字符可能在坏字符位置的前面,也有可能在后面。所以使用坏字符规则,移动的距离可能为负数,这个时候就需要另外一种规则好后缀规则。

    好后缀规则:模式串B与主串对比遇到坏字符时,有一部分已经匹配好,这部分就叫做好后缀。B继续从后往前查询,如果还存在好后缀这样的子串,则移动到相应位置。如果不存在,就匹配好后缀最长的后缀子串,如果都无法匹配就跨越整个模式串。

    最后每次从坏字符规则和好后缀规则中,获取较大移动位置的那个值,移动相应距离即可。

    KMP算法:  模式串B与主串逐个字符对比,相同则一直往下比较。如果遇到不相等的字符,前面已经匹配好的字符串则为好前缀C。从好前缀中获取最大匹配好前缀子串的坐标,比较其后一个字符是否和坏字符相等,不相等继续直到循环终止。


    BM算法如下:

    /**

        *

        * @param a 主串

        * @param n 主串长度

        * @param b 模式串

        * @param m 模式串长度

        * @return

        */

        public int bm2(char[] a, int n, char[] b, int m)

        {

            int[] bc = new int[SIZE];

            generateBC(b,m,bc);

            int[] suffix = new int[m];

            boolean[] prefix = new boolean[m];

            generateGS(b,m,suffix,prefix);

            int i = 0;// j表示主串与模式串匹配的第一个字符

            while (i <= n - m){

                int j;

                for (j = m - 1; j >= 0 ; --j){

                    if (a[i+j] != b[j]) break;// 坏字符对应模式串中的下标

                }

                if (j < 0){

                    return i;

                }

                int x = j - bc[a[i+j]];

                int y = 0;

                if ( j < m-1){

                    y = moveByGS(j,m,suffix,prefix);

                }

                i = i + Math.max(x,y);

            }

            return -1;

        }

        private void generateBC(char[] b,int m, int[] bc){

            for (int i = 0; i < SIZE; ++i){

                bc[i] = -1;

            }

            for (int i = 0; i < m; ++i){

                int ascii = (int)b[i];

                bc[ascii] = i; // 如果ascii相同只需要存储 bc[ascii] = 最后一个

            }

        }

        /**

        *

        * @param b = 模式串

        * @param m = 表示长度

        * @param suffix = 数组

        * @param prefix

        */

        private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix)

        {

            for (int i = 0; i < m ; ++i){ //初始化

                suffix[i]  = -1;

                prefix[i] = false;

            }

            for (int i = 0; i < m - 1; ++i){ // b[0,i]

                int j = i;

                int k = 0;

                while (j >= 0 && b[j] == b[m-1-k]){

                    --j;

                    ++k;

                    suffix[k] = j + 1;//记录模式串每个可以匹配前缀子串的长度 等于 最大下标值

                }

                if (j == -1) prefix[k] = true;

            }

        }

        /**

        *

        * @param j 表示坏字符对应模式串中的字符下标

        * @param m 模式串长度

        * @param suffix

        * @param prefix

        * @return

        */

        private int moveByGS(int j,int m,int[] suffix, boolean[] prefix)

        {

            int k = m - 1 -j; // 好后缀的长度

            if(suffix[k] != -1) return j - suffix[k] + 1;

            for (int r = j + 2; r <= m-1; ++r){

                if (prefix[m-r] == true) return r;

            }

            return m;

        }


    KMP算法如下:

    /**

        * KMP算法

        * @param a

        * @param n

        * @param b

        * @param m

        * @return

        */

        public static int kmp(char[] a, int n, char[] b, int m)

        {

            int[] next = getNexts(b,m);//获取模式子串的最长前缀匹配子串的下表

            int j = 0;

            for (int i = 0; i < n; ++i){

                while (j>0&&a[i]!=b[j]){//模式串和主串不等,模式串对应下表为j, 匹配的前缀为最后下标为j-1, 获取j-1的最大匹配前缀,比较 前缀+1 和 坏字符是否相同即可

                    j = next[j-1]+1;

                }

                if (a[i] == b[j]){//如果主串和模式串一样

                    ++j;

                }

                if (j == m) return i - m + 1; // i是下标,m是长度 所有需要 i + 1 - m

            }

            return -1;

        }

        /**

        * b表示模式串,m表示模式串的长度

        * @param b

        * @param m

        * @return

        */

        private static int[] getNexts(char[] b, int m)

        {

            int[] next = new int[m];

            next[0] = -1; // 只有一个字符的前缀时,没有前缀

            int k = -1;

            for (int i = 1; i < m; ++i){ //遍历模式串 1 到 m-1

                /**

                * 如:模式串 ababacd.

                * i从[1,6],k从[0,5]

                * i = 1, k = -1 b[0] != b[1] => next[1] = -1

                * i = 2, k = -1 b[0] = b[2] ++k => next[2] = 0

                * i = 3, k = 0  b[1] = b[3] ++k  => next[3] = 1

                * i = 4, k = 1 b[2] = b[4] ++k => next[4] = 2

                * i = 5, k = 2 b[3] != b[5]  k = next[2] = 0, b[1] != b[5]  k = next[0] = -1 => b[0] != b[5] , next[5] = -1

                * i = 6, k = -1 b[0] != b[6] => next[6] = -1

                */

                while (k != -1 && b[k+1] != b[i]){ // 如果 在前基础上下个字符不相等时,就会比较最长比较子串的 最长匹配字串,就是次长匹配子串,直到找到为止

                    k = next[k];

                }

                if (b[k+1] == b[i]){ // 思想next[2] = 0,表示 aba =》 a?ba? 只要 ?=?就会有 next[3] = 1

                    ++k;

                }

                next[i] = k;

            }

            return next;

        }

    相关文章

      网友评论

        本文标题:字符串匹配算法总结

        本文链接:https://www.haomeiwen.com/subject/cpwvtctx.html