KMP算法详解与应用

一、 前言

  • 时间复杂度O(n)
  • 解决包含问题

举例:判断str2是否包含于str1
str1:abc123def
str2:123d

解决方法 (str1长度为n, str2长度为m):

  1. 笨办法: 两个for循环遍历来匹配,复杂度O(n*m)
  2. KMP方法: 利用next数组j减少匹配次数

int getIndexOf(str1,str2) 就是KMP实现的

二、代码实现

KMP步骤一:先把str2计算出最长前缀和最长后缀相等的next数组

public static int[] getNextArray(char[] str2){
    if(str2.length == 1){
        return new int[]{-1};//如果只有一个,没有最长前缀,即-1
    }
    int[] next = new int[str2.length];
    next[0] = -1; 
    next[1] = 0;  //前两位默认
    int i = 2;
    int cn = 0; //标记回退的位置
    while(i < next.length){
        if(str2[i-1]==(str2[cn])){
            next[i++] = ++cn;
        }else if(cn > 0){
            cn = next[cn];  //回退到上一个位置,继续判断是否有最长
        }else{
            next[i++] = 0;  //没有最长前缀等于最长后缀的情况
        }
    }
    return next;
}

KMP算法实现getIndexOf

public static int getIndexOf(String s, String m){
    if(s==null || m==null || m.length() < 1 || s.length() < m.length()){
        return -1;
    }
    char[] str1 = s.toCharArray();
    char[] str2 = m.toCharArray();
    int i1 = 0; //str1的下标
    int i2 = 0; //str2的下标
    int[] next = getNextArray(str2);
    while(i1 < str1.length && i2 < str2.length){
        if(str1[i1] == str2[i2]){
            i1++;
            i2++;
        }else if (next[i2] == -1){ //第一个位置都没匹配,此时str2不动
            i1++;
        }else{
            i2 = next[i2]; //st2可以往前跳
        }
    }
    return i2 == str2.length? i1 - i2:-1; 
}

三、 题目类型

3.1 字符串追加

举例:原始串abcabc,只能在后面添加字符得到字符串(包含里两个原始串,最长前缀和最长后缀等于原字符串),如何处理的大串最短。
答案:abcabcabc。
解题思路:就是求next数组,然后再求一个终止位置的next,,然后往前的字符串逆序补到字符串尾部。

Java 代码实现:

 public static int[] getNextArray(char[] str){
	if(str.length == 1){
		return new int[]{-1};
	}
	int[] next = new int[str.length];
	next[0] = -1;
	next[1] = 0;
	int i = 2;
	int cn = 0;
	while(i<str.length){
		if(str[i-1]==str[cn]){
			next[i++] = ++cn;
		} else if(cn > 0){
			cn = next[cn];
		} else {
			next[i++] = 0;
		}
	}
	return next;
}

public static String process(char[] str){
	int[] next = getNextArray(str);
	int location = -1;
	int cn = next[str.length-1];
	while(location == -1){
		if(str[str.length-1] == str[cn]){
			location = next[str.length-1] + 1;
		} else if (cn > 0){
			cn = next[cn];
		} else {
			location = 0;
		}
	}
	String result= "";
	int resultLength = 2*next.length - location;
	for (int i = 0; i < resultLength; i++) {
		if(i < next.length){
			result += str[i];
		} else {
			result += str[location];
			location ++;
		}
	}
	return result;
}

public static void main(String[] args) {
	String str = "abcabc";
	String result = process(str.toCharArray());
	System.out.println(result);
}

3.2 子树问题

题目:两棵树T1和T2,可以值相同,可以值不同。T1中的某一个子树是否等于T2。
解题思路:T1的序列化和T2的序列化。如果S2是S1的子串,那么T2就是T1的子树

Java 代码实现:

public static boolean getIndexOf(String str1, String str2){
	if(str1==null || str2==null || str2.length()<0||str2.length()>str1.length()){
		return false;
	}
	String[] arr1 = str1.split("_");
	String[] arr2 = str2.split("_");
	int[] next = getNextArray(arr2);
	int i = 0;
	int j = 0;
	while(i < arr1.length && j < arr2.length){
		if(arr1[i].equals(arr2[j])){
			i++;
			j++;
		}else if(next[j] == -1){
			i++;
		}else{
			j = next[j];
		}
	}
	return j == arr2.length; //str全部被匹配,返回true
}

public static int[] getNextArray(String[] arr){
	int[] next = new int[arr.length];
	next[0] = -1;
	next[1] = 0;
	int i = 2;
	int cn = 0;
	while(i < arr.length){
		if(arr[i-1].equals(arr[cn])){
			next[i++] = ++cn;
		}else if(cn > 0){
			cn = next[cn];
		}else{
			next[i++] = 0;
		}
	}
	return next;
}

public static String serialTree(Node node){
	if(node==null){
		return "_"; //与通常的序列化不同,不需要占位
	}
	String res = node.value + "_";
	res += serialTree(node.lChild);
	res += serialTree(node.rChild);
	return res;
}

public static void main(String[] args) {
	Tree tree1 = new Tree(new Node(8));
	tree1.add(9);
	tree1.add(2);
	Tree tree2 = new Tree(new Node(8));
	tree2.add(8);
	tree2.add(7);
	tree2.add(9);
	tree2.add(2);
	tree2.add(4);
	System.out.println("".length());
	System.out.println(HasSubtree(tree2.root,tree1.root));
}

序列化的时候要改变:不能把null用占位符,否则结果会受到null的影响
举例:T1[8,8,7,9,2,#,#,#,#,4,7] T2[8,9,2,#,#,#,#\]
先序遍历方式序列化:T1[889##2477##] T2[892##2##\]
由于节点2(T1)下挂着节点47, 节点2(T2)挂着nullnull,导致序列化结果不匹配。
若不是节点2下,而是节点9下挂着47节点,会导致T1[889472##7##] T2[892##2##],在匹配前半段就不匹配了。

3.3 字符串重复判断

题目:怎么确定一个字符串是不是另一个字符串重复得到的,是返回true,不是返回false
即:str = str’*n
如:123123123123

你可能感兴趣的:(教程)