字符串之KMP、Manacher算法

1.Manacher 算法

原理介绍:Manacher算法相对于KMP算法来说,用途还是比较浅的,该算法只适用于求字符串中最长回文串的长度以及回文串的数组。

  • 首先对原字符串进行改造

原因:因为一般的字符串有可能是奇数也有可能是偶数,所以我们不得不去分类考虑这些情况(奇数偶数),但是我们对它进行改造,把它变为奇数位的一个字符串

abab—>#a#b#a#b# 4-->9

ababa—>#a#b#a#b#a# 5-->11

  • 在我们改造后的数组中存在以下两大种情况

      pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
    • i>R

    • i<R

Manacher算法的具体流程就是先匹配 -> 通过判断i与R的关系进行不同的分支操作 -> 继续遍历直到遍历完整个字符串

//求解回文串的最大长度

 //Manacher 算法  马拉车算法  将奇偶数的字符串通过操作转换为奇数字符串进行统一操作
    public int longestPalindrome(String s) {
        //对原字符串记性判断
        if (s == null || s.length() == 0) {
            return 0;
        }
        //如果该字符串原本就是一个字符,直接返回原字符串
        if (s.length() == 1) {
            return 0;
        }
        //通过该方法去得到一个经过改变的字符数组
        char[] str = manacherString(s);
        //获取到长度
        int n = str.length;
        int[] pArr = new int[n];//回文半径数组
        int C = -1;//中心
        int R = -1;//回文串右边界再往右一个位置,右边的有限区域是在R-1的位置上
        int center=0;
        int max = Integer.MIN_VALUE;//扩出来的最大值
        //对改变后的字符数组每一个元素进行求解回文半径
        for (int i = 0; i < n; i++) {
            //相当于对回文半径数组进行填充
            pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
            //找到再继续向两边进行扩展
            while(i+pArr[i]<n&&i-pArr[i]>=0){
                if(str[i+pArr[i]]==str[i-pArr[i]]){
                    pArr[i]++;
                }else{
                    break;
                }
            }
            //进行更新
            if(i+pArr[i]>R){
                R=i+pArr[i];
                C=i;
            }
            if(pArr[i]>max){
                center=i;
                max=pArr[i];
            }
        }
        return max-1;
    }
 //将原字符串进行特殊的改变
    char[] manacherString(String s) {
        //aba--->*a*b*a*
        //声明一个长度2*s.length+1的字符数组
        char[] str = new char[2 * s.length() + 1];
        //对新创建的数组进行填充
        int index = 1;
        //第一种方法对新创建的数组进行遍历
//        for (int i = 0; i < str.length; i++) {
//            //根据我们观察,奇数位置上的位置都是我们添加的新字符
//            str[i] = (i & 1) == 0 ? '*' : s.charAt(index++);
//        }
        //第二种方法对原字符串进行遍历
        str[0]='*';
        for (int i = 0; i < s.length(); i++) {
            str[index++] = s.charAt(i);
            str[index++] = '*';
        }
        return str;
    }

//求得最长回文子串

 StringBuilder sb = new StringBuilder();
        //如果该中心位置不是为特殊字符,直接添加到StringBuilding中
        if (str[center] != '*') {
            sb.append(str[center]);
        }
        //往两边进行扩展
        int left = center - 1;
        int right = center + 1;
        //如果两边是相同的字符,则同时左-- 右++;   相当于就是为了还原改变后的数组   在原数组中求得最长回文子串
        while (left >= 0 && right <n && str[left] == str[right]) {
            if (str[left] != '*') {
                sb.insert(0, str[left]);
                sb.append(str[right]);
            }
            left--;
            right++;
        }
        return sb.toString();

2.KMP算法(字符串检索的算法)

原理:

不用进行重复比较,直接找到最长的前缀字符,在这之前我们首先先要求得模式串中next数组

 private static int[] computeLPSArray(String pattern) {
        int[] next = new int[pattern.length()];
        next[0]=-1;
        next[1]=0;
        int i=2;//提前走了一步
        //next数组中的元素,记录前缀最长匹配长度的索引
        int k=0;//前一项的k
        for (int j =2; j <pattern.length(); j++) {
              if(k==-1||pattern.charAt(j-1)==pattern.charAt(k)){
                  next[i]=k+1;
                  k++;
                  i++;
              }else{
                  k=next[k];
              }
        }
        return next;
    }

 方法一:

public class KMPAlgorithm {
    public static int kmp(String text, String pattern) {
        int[] lps = computeLPSArray(pattern); // 计算模式字符串的LPS数组
        int i = 0; // text中的索引
        int j = 0; // pattern中的索引
​
        while (i < text.length()) {
            if (text.charAt(i) == pattern.charAt(j)) { // 当前字符匹配成功
                i++; // 继续比较下一个字符
                j++;
                if (j == pattern.length()) {
                    return i - j; // 找到完全匹配,返回匹配的起始位置
                }
            } else {
                if (j != 0) { // 如果pattern的前缀中存在部分匹配
                    j = lps[j - 1]; // 根据LPS数组进行跳转
                } else {
                    i++; // 没有匹配时,移动text的指针
                }
            }
        }
        return -1; // 未找到匹配
    }
​
    private static int[] computeLPSArray(String pattern) {
        int[] lps = new int[pattern.length()]; // 初始化LPS数组
        int len = 0; // 前缀中的最长公共长度
        int i = 1;
​
        while (i < pattern.length()) {
            if (pattern.charAt(i) == pattern.charAt(len)) { // 当前字符匹配成功
                len++; // 前缀长度加1
                lps[i] = len; // 更新LPS数组中对应位置的值
                i++; // 继续比较下一个字符
            } else {
                if (len != 0) { // 前缀中存在部分匹配
                    len = lps[len - 1]; // 根据LPS数组进行跳转
                } else {
                    lps[i] = 0; // 没有匹配时,LPS数组对应位置为0
                    i++; // 继续比较下一个字符
                }
            }
        }
        return lps; // 返回计算得到的LPS数组
    }
​
    public static void main(String[] args) {
        String text = "ABABDABACDABABCABAB"; // 待搜索的文本
        String pattern = "ABABCABAB"; // 要搜索的模式
        int index = kmp(text, pattern); // 调用KMP算法进行匹配
        if (index != -1) {
            System.out.println("匹配发生在索引 " + index); // 输出匹配的起始位置
        } else {
            System.out.println("未找到匹配");
        }
    }
}
​

方法二:

class KMPAlgorithm {
    public static int kmp(String text, String pattern, int pos) {
        //对入参进行判断
        //pos是对主串开始匹配的索引位置
        if (text == null || pattern == null) return -1;
        if (text.length() == 0 || pattern.length() == 0) return -1;
        int textLen = text.length();
        int patternLen = pattern.length();
        if (pos < 0 || pos >= textLen) return -1;
        int[] next = computeLPSArray(pattern);
        //主串开始的索引
        int i = pos;
        //模式串匹配的索引
        int j = 0;
        while (i < textLen && j < patternLen) {
            if ( j == -1||text.charAt(i) == pattern.charAt(j) ) {
                i++;
                j++;
            } else {
                j = next[j];
            }
        }
        if (j >= patternLen) {
            return i - j;
        }
        return -1;
    }
​
    private static int[] computeLPSArray(String pattern) {
        int m = pattern.length();
        int[] next = new int[m];
        //通常next数组第一个就是为-1
        next[0] = -1;
        int i = 0, k = -1;
        while (i < m - 1) {
            //i代表的还是0~i-1所具有的最长前缀长度
            if (k == -1 || pattern.charAt(i) == pattern.charAt(k)) {
                i++;
                k++;
                next[i] = k;
            } else {
                //等于
                k = next[k];
            }
        }
        return next;
    }
​
    public static void main(String[] args) {
        String text = "ABABDABACDABABCABAB"; // 待搜索的文本
        String pattern = "ABABCABAB"; // 要搜索的模式
        int index = kmp(text, pattern,0); // 调用KMP算法进行匹配
        if (index != -1) {
            System.out.println("匹配发生在索引 " + index); // 输出匹配的起始位置
        } else {
            System.out.println("未找到匹配");
        }
    }

猜你喜欢

转载自blog.csdn.net/dfdbb6b/article/details/131555516