字符串匹配算法

目录

    • BF
    • KMP
      • next数组
      • 匹配
      • 代码
    • SunDay

BF

  • 最简单易懂的写法,也是性能最差的写法。
  • 最坏时间复杂度:O(n*m)
    /**
     * @param str1 主串
     * @param str2 子串
     * @param pos  从主串的pos位置开始找.
     * @return
     */
    public static int BF(String str1,String str2,int pos) {
        int lenstr1 = str1.length();
        int lenstr2 = str2.length();
        //对pos的位置合法判断
        if (pos < 0 || pos > lenstr1) {
            return -1;
        }
        int i = pos;  //遍历主串,
        int j = 0;    //遍历子串
        while (i < lenstr1 && j < lenstr2) {  //主串和子串都没有遍历完成的情况下,比较字符是否相等
            if (str1.charAt(i) == str2.charAt(j)) { //下标所对应的字符相等时,
                i++;
                j++;
            } else {   //匹配失败
                i = i - j + 1; //主串从下一个字符开始匹配
                j = 0;		   //子串从第一个字符开始匹配
            }
        }
        if (j >= str2.length()) { //子串遍历完成则返回匹配成功时 i 的起始位置。
            return i - j;
        } else {   // 没找到
            return -1;
        }
    }

KMP

  • 主要分为两步,第一步,求next数组,第二步,匹配

next数组

  • next数组是对于一个字符串而言的,比如我们求str2str1中是否存在,如果存在返回str2str1中的起始索引,那么我们就要求出str2next数组
  • 那字符串的next数组怎么求呢。
    • 假设字符串abcabct,对于t,它前面的字符串是abcabcabcabc的前缀为aababcabcaabcab;它的后缀cbcabccabcbcabc(前缀不能包含最后一位,后缀不能包含最前一位,所以前缀不能是abcabc,后缀也不能是abcabc)。前缀与后缀中最长的相等的字符串是abc,所以tnext数组值为abc的长度,即为3
    • 同理,对于c,它前面的字符串为abcab,前缀为aababcabca;后缀为babcabbcab。前缀与后缀中最长的相等的字符串是ab,所以c位置next数组值为2
    • 倒数第3个字符b求法类推……
    • 对于最开始的a,由于它是第一个字符,前面没有字符串,所以人为规定它的next数组值为-1,也可以规定为0,但定为-1对后面有用,第二个字符b前面只有a,而前缀和后缀不能包含最后一个和第一个字符,所以第二个字符的next0。除了第一个第二个特殊,其他的均用以上方法求next数组值
    • 所以,对于字符串abcabct,它的next数组值为[-1,0,0,0,1,2,3]

匹配

  • 得到next数组后怎么用呢?
  • 比如str1abcabckxyzstr2abcabct,显然是kt不相等,而对于str2t位置next数组值为3,所以就从t开始往前找3位,找到了str2中的第二个a,它对应str1中的第二个a,就将str2后移,使str2的起始位置元素对应str1中找到的这个元素。
  • 为什么这么做呢?
  • 首先,kt不相等,说明kt的前面都是相等的
    • 如图,str1i位置开始,str20位置开始,进行匹配,到XY处发现不等,那么第一坨与第二坨肯定是相等的。并且第一坨中任意一段一定与第二坨中对应段上的字符串相等。
      字符串匹配算法_第1张图片
  • tnext值表示t前面的串中最长的相等的前缀和后缀的长度
    • 如图,由上面加粗字体我们可以知道,第三坨字符串肯定与后缀段字符串相等,由Y位置的next数组值我们知道Y前面的串中最长的相等的前缀与后缀,也就是图中前缀段字符串与后缀段字符串相等,它们的长度就是Y位置的next数组值。

字符串匹配算法_第2张图片

  • 所以,前缀段字符串与后缀段字符串相等,后缀段字符串与第三坨字符串相等,那么前缀段字符串就与第三坨字符串相等,那么我们就可以把第三坨字符串与前缀段字符串移到对应位置上,因为它们相等,所以下次匹配就从X位置和前缀字符串的后一个位置开始匹配。

字符串匹配算法_第3张图片

  • 也就是将下图中的X位置与Z位置开始匹配,如果相同,分别匹配后一位,如果不相同,就采用上述方法,根据Z位置的next数组值,得到Z的前缀段字符串和后缀段字符串,这里不再赘述。
  • 如果Z位置的next数组值为0,说明Z前面的字符串没有相等的前缀段与后缀段,那么把str20位置与str1X位置对齐即可。
    字符串匹配算法_第4张图片

代码

     /**
     * 判断s1中是否存在s2,如果存在,返回匹配的起始索引,不存在返回-1
     * @param s1
     * @param s2
     * @return
     */
    public static int getIndexOf(String s1,String s2){
        if(s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()){
            return -1;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        // str1从i1位置开始,str2从i2位置开始,进行匹配
        int i1 = 0;
        int i2 = 0;
        // 得到next数组
        int [] next = getNextArray(str2);
        while (i1 < str1.length && i2 < str2.length){
            // 如果i1,i2位置的字符相同,i1,i2都后移
            if(str1[i1] == str2[i2]){
                i1 ++;
                i2 ++;
            }else{// 如果i1,i2位置的字符不同,匹配失败
                // i2的next数组值为-1(之前说了,第一个位置的next数组值人为规定为-1有用,就在这里用),
                // 说明i2是str2的开头,那么就从i1的下一个位置开始匹配
                if(next[i2] == -1){
                    i1 ++;
                }else{
                    // i2不是str2的开头,就把str2后移,后移多少呢,
                    // 要把i2的前缀段字符串与第三坨字符串移到对齐的位置,下次匹配时,从
                    // 前缀段字符串的后一个位置开始匹配,而i2就是表示从str2的哪个位置开始匹配,
                    // 并且前缀段字符串的长度为next[i2],所以i2 = next[i2],
                    i2 = next[i2];
                }
            }
        }
        //i2 == str2.length,说明整个str2都匹配到了,否则返回-1
        return i2 == str2.length ? i1 - i2 : -1;
    }

    /**
     * 求next数组
     * @param str
     * @return
     */
    private static int[] getNextArray(char[] str) {
        if(str.length == 1){
            return new int[]{-1};
        }
        int[] next = new int[str.length];
        //第一个字符的next数组值规定为-1,第二个字符的next数组值规定为0
        next[0] = -1;
        next[1] = 0;
        //从i开始,求[i,str.length)的next数组值
        int i = 2;
        // cn表示跳到的位置
        int cn = 0;
        while (i < next.length){
            //跳到的位置与i的前一个位置字符相同,那么next[i] = cn + 1
            if(str[i - 1] == str[cn]){
                next[i ++] = ++cn;
            }else if(cn > 0){
                cn = next[cn];
            }else{
                next[i ++] = 0;
            }
        }
        return  next;
    }

    public static void main(String[] args) {
        System.out.println(getIndexOf("abdefbcd","bcd"));
    }

SunDay

public class TestSunDay {
 
	public static void main(String[] args) {
		
		String source = "acfdedfacfdbea";
		String target = "acfdbe";
 
		int i = SunDay(source.toCharArray(),target.toCharArray(),0);
		System.out.println("匹配结果:" + i);
	}
	
	/**
	 * 
	 * @param tempS 主串
	 * @param tempT 子串
	 * @param indexS 遍历主串是从indexS下标处开始遍历
	 * @return
	 */
	public static int SunDay(char[] tempS, char[] tempT,int indexS) {
		if(indexS >= tempS.length)
			return -1;
		//遍历子串是从indexT下标处开始遍历
		int indexT = 0;
		//计数匹配了多少个字符
		int count = 0;
		for (int i = indexS; i < indexS + tempT.length; i++) {
			if (tempS[i] == tempT[indexT]) {
				indexT ++;
				count ++;
			} else {
				break;
			}
		}
		//匹配的字符数和子串的长度相等,即匹配成功
		if (count == tempT.length) {
			return indexS;
		}	
		//匹配失败,看后面一个字符在子串中有没有相同的
		if(indexS + tempT.length < tempS.length){
			int i = check(tempS[indexS + tempT.length],tempT);
			if( i == -1){
				//没有相同的就子串就后移子串长度个位置
				indexS += tempT.length;
				//再重新匹配
				return SunDay(tempS, tempT, indexS);
			}else{
				//后面一个字符在子串中有相同的,子串往后移若干位,使得这两个字符对其
				indexS = indexS + tempT.length - i;
				return SunDay(tempS, tempT, indexS);
			}
		}else{
			return -1;
		}
	}
 
	
	/**
	 * 判断在tempT数组中有没有c这个字符
	 * @param c
	 * @param tempT 
	 * @return 返回c在tempT中的下标,没有就返回-1
	 */
	public static int check(char c, char[] tempT) {
		for (int i = 0; i < tempT.length; i++) {
			if(c == tempT[i])
				return i;
		}
		return -1;
	}
		
}

你可能感兴趣的:(数据结构与算法,java)