在母串內(nèi)查找子串 KMP 算法

之前了解了下這個算法,實現(xiàn)了下,有問題歡迎指教

package org.huangry.colorful.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

/**
 * ClassName: KMPUtil
 *
 * @author huangruiying
 * @version 1.0
 */
public class KMPUtil {

    private Logger logger = LoggerFactory.getLogger(KMPUtil.class);

    //計算后的序列
    private static int[] substring_sequence_arr;

    private String parentStr = "";
    private String childStr = "";

    public KMPUtil(String parentStr, String childStr){
        this.parentStr = parentStr;
        this.childStr = childStr;
        //處理子串:生成子串序列到substring_sequence_arr
        generate_substring_sequence(childStr);
        logger.info(">>> KMP init success");
        logger.info(">>> substr_seq_array {}",Arrays.toString(substring_sequence_arr));
    }

    /**
     * 查詢母串內(nèi)是否包含子串
     * 然后返回索引
     *  不包含時 索引為 -1
     *  包含時 索引為第一次出現(xiàn)的位置
     */
    public int start(){
        //通過KMP查詢
        int index = search(parentStr,childStr);
        logger.info("子串在目標(biāo)字符串的索引: {}", index);
        return index;
    }

    /**
     * 獲取母串內(nèi)包含子串的個數(shù)
     * @return
     */
    public int getCount(){
        return count(parentStr,childStr);
    }

    /**
     * KMP查詢
     * @author huangruiying
     * @param target 目標(biāo)字符串
     * @param substring 待查詢子串
     * @return 查找到的子串在目標(biāo)字符串中的起始索引
     */
    private int search(final String target, final String substring) {

        final char[] target_arr = target.toCharArray();
        final char[] substring_arr = substring.toCharArray();
        //初始化游標(biāo) i:target游標(biāo) j:子串游標(biāo)
        int i=0,j=0;

        //邊界
        while (i < target_arr.length){
            char target_val = target_arr[i];
            char substring_val = substring_arr[j];
            if(target_val == substring_val){
                //跳出條件 (-1 : 長度跟索引比較,所以要-1)
                if(j == (substring.length()-1)){
                    /**
                     * 返回起始索引
                     * target&index:sssa&0123
                     * substr&index:sa&01
                     * 返回 (3-1)= 2 , 2為substring sa第一字在target中出現(xiàn)的索引位置。
                     */
                    return i-j;
                }
                i++;
                j++;
            }else{
                int before_j = j - 1;
                //子串邊界
                if(before_j < 0){
                    j = 0;
                }else{
                    j = substring_sequence_arr[before_j];
                }
                //未尋找到時的跳出條件
                if(i == (target.length()-1)){
                    return -1;
                }
            }
        }

        return -1;
    }

    private int count(final String target, final String substring) {

        int count = 0;// 計數(shù) target 內(nèi)包含多少個 substring

        final char[] target_arr = target.toCharArray();
        final char[] substring_arr = substring.toCharArray();
        //初始化游標(biāo) i:target游標(biāo) j:子串游標(biāo)
        int i=0,j=0;

        //邊界
        while (i < target_arr.length){
            char target_val = target_arr[i];
            char substring_val = substring_arr[j];
            if(target_val == substring_val){
                if(j == (substring.length()-1)){
                    count ++ ;
                    i ++ ;
                    j = 0;
                    continue;
                }
                i++;
                j++;
            }else{
                int before_j = j - 1;
                //子串邊界
                if(before_j < 0){
                    j = 0;
                }else{
                    j = substring_sequence_arr[before_j];
                }
                //未尋找到時的跳出條件
                if(i == (target.length()-1)){
                    return count;
                }
                i ++ ;
            }
        }
        return count;
    }

    /**
     * 生成子串序列
     * 子串:abcdabca
     * 序列:00001231
     * 規(guī)則:使用i,j兩游標(biāo),
     * 初始化變量i=0,j=1
     * 初始化序列數(shù)組 0 0 0 0 0 0 0 0
     * 對比i與j所在索引處的數(shù)組值是否相等
     * 若相等,那么j處序列數(shù)組值為i+1(索引+1) ,然后i,j分別向后移動一位
     * 若不相等,查看序列數(shù)組內(nèi)當(dāng)前i-1位置的值(索引),并將該值賦值給當(dāng)前變量i 結(jié)束本次循環(huán)(注意i的邊界情況)
     * @author huangruiying
     * @param substring 子串
     */
    private void generate_substring_sequence(String substring){
        substring_sequence_arr = new int[substring.length()];
        char[] substring_arr = substring.toCharArray();
        //游標(biāo)
        int i=0,j=1;
        while (j<substring_arr.length){
            char iChar = substring_arr[i];
            char jChar = substring_arr[j];
            if(iChar == jChar){
                substring_sequence_arr[j] = i+1;
                j++;
                i++;
            }else{
                int before_i = i-1;
                //邊界
                if(before_i < 0){
                    substring_sequence_arr[j] = 0;
                    j++;
                }else{
                    //通過序列內(nèi)當(dāng)前i的前一個值,改變i游標(biāo)
                    i = KMPUtil.substring_sequence_arr[before_i];
                }
            }
        }
    }


}



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。