串
串的定义
串是由零个或多个字符组成的有限序列,又名叫字符串。一般记为s="a1a2a3...an"(n>=0)
串中的字符数目n称为串的长度。
n==0的串也就是零个字符的串称为空串,长度为零,使用""
表示。
空格串,是指只包含空格的串,空格串是有长度的。
子串个数
串中任意字符的长度的子序列称为子串,子串在主串中的位置就是子串的第一个字符在主串中的序号。
长度为n的串的非空子串个数F(n)是
考虑空子串的话要加一
串的操作
和线性表不同,串更关注的是查找子串的位置,得到指定位置的子串和替换子串等。
朴素的模式匹配
步骤
简单地说,就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,知道匹配成功或者全部遍历为止。
代码
package com.weixuan.test;
public class Bunch {
private static final Integer FALSE = -1;
/**
* @Description: 返回子串target在主串original中第i个字符之后
* @param @param
* original
* @param @param
* target
* @param @return
* @return int
*/
public int getIndex(String original, String target) {
if (original == null || original.length() <= 0)
return Bunch.FALSE;
if (target == null || target.length() <= 0)
return Bunch.FALSE;
int j = 0, i = 0;
while (i < original.length() && j < target.length()) {
if (original.charAt(i) == target.charAt(j)) {
i++;
j++;
} else {
/**
* 后退到上次匹配的位置的下一位
*/
i = i - j + 1;
j = 0;
}
}
if (j >= target.length())
return i - target.length();
return Bunch.FALSE;
}
public static void main(String[] args) {
System.out.println(new Bunch().getIndex("abcdefg", "dbc"));
}
}
性能
主串长度 :M,子串长度:N
最好的情况:O(1)
平均情况:O(M+N),平均是(m+n)/2次找
最差情况,每次不成功的匹配都是发生在子串的最后一个字符,O((M−N+1)∗N)
KMP
next数组
S是主串,T是子串。j值的变换与主串没什么关系,j值取决于当前字符之前的串的前后缀的相似程度。
几个例子:
T = “abcdex”
j=1,next[1]=0
j=2,j 由 1到 j−1 的字符只有a
,属于其他情况 next[2]=1
j=3,j,由 1到 j−1 的字符只有ab
,显然a
与b
不相等,属于其他情况 next[3]=1
j=4,j由 1到 j−1 的字符只有abc
,显然a
与b
与c
不相等,属于其他情况 next[4]=1
以后同理,所以T串的next[j]如下
j | 1 2 3 4 5 6 |
---|---|
next[j] | 0 1 1 1 1 1 |
2. T = “abcdex”
j=1,next[1]=0
j=2,j 由 1到 j−1 的字符只有a
,属于其他情况 next[2]=1
j=3,j 由 1到 j−1 的字符只有ab
,显然a
与b
不相等,属于其他情况 next[3]=1
j=4,j 由 1到 j−1 的字符只有abc
,显然a
与b
与c
不相等,属于其他情况 next[4]=1
j=5,j 由 1到 j−1 的字符为abca
,前缀字符a与后缀字符相等,p1==p4,由公式p1...pk−1=pj−k+1...pj−1 可得,j−k+1=4,j=5,==>k=2,即k = 2.所以next[5]=2
j=6,j由 1到 j−1 的字符为abcab
,前缀字符ab
与后缀字符ab
相等,p1p2==p4p5,由公式p1...pk−1=pj−k+1...pj−1 可得,j−k+1=4,j=6,==>k=3,即k = 3.所以next[6]=3
所以T串的next[j]如下
j | 1 2 3 4 5 6 |
---|---|
next[j] | 0 1 1 1 2 3 |
3. T = “ababaaaba”
j=1,next[1]=0
j=2,j 由 1到 j−1 的字符只有a
,属于其他情况 next[2]=1
j=3,j 由 1到 j−1 的字符只有ab
,显然a
与b
不相等,属于其他情况 next[3]=1
j=4,j 由 1到 j−1 的字符只有aba
,前缀与后缀相等,根据公式,j−k+1=3,j=4,k=2 next[4]=2
j=5,j 由 1到 j−1 的字符为abab
,前缀字符ab
与后缀字符ab
相等,p1p2==p3p4,由公式p1...pk−1=pj−k+1...pj−1 可得,j−k+1=3,j=5,==>k=3,即k = 3.所以next[5]=3
j=6,j由 1到 j−1 的字符为ababa
,前缀字符aba
与后缀字符aba
相等,p1p2p3==p3p4p5,由公式p1...pk−1=pj−k+1...pj−1 可得,k−1=3,k=4,即k = 4.所以next[6]=4
j=7,j由 1到 j−1 的字符为ababaa
,前缀字符ab
与后缀字符aa
不相等,只有a相等,p1==p6,由公式p1...pk−1=pj−k+1...pj−1 可得,k−1=1,k=2,即k = 2,所以next[7]=2
j=8,j由 1到 j−1 的字符为ababaaa
,前缀字符ab
与后缀字符aa
不相等,只有a相等,p1==p7,由公式p1...pk−1=pj−k+1...pj−1 可得,k−1=1,k=2,即k = 2,所以next[8]=2
j=9,j由 1到 j−1 的字符为ababaaab
,前缀字符ab
与后缀字符ab
相等,p1p2==p7p8,由公式p1...pk−1=pj−k+1...pj−1 可得,k−1=2,k=3,即k = 3,所以next[9]=3
所以T串的next[j]如下
j | 1 2 3 4 5 6 7 8 9 |
---|---|
next[j] | 0 1 1 1 2 3 2 2 3 |
T = “aaaaaaaab”
j=1,next[1]=0
j=2,j 由 1到 j−1 的字符只有a
,属于其他情况 next[2]=1
j=3,j 由 1到 j−1 的字符只有aa
,显然a
与a
相等,p1==p2,k−1=1,k=2 next[3]=2
j=4,j 由 1到 j−1 的字符只有aaa
,显然前缀aa
与后缀aa
相等,p1p2==p2p3,k−1=2,k=3 next[4]=3
….
j=9,j 由 1到 j−1 的字符是aaaaaaaa
,显然前缀aaaaaaa
与后缀aaaaaaa
相等,p1p2p3p4p5p6p7==p2p3p4p5p6p7p8,k−1=7,k=8 next[9]=8
j | 1 2 3 4 5 6 7 8 9 |
---|---|
next[j] | 0 1 2 3 4 5 6 7 8 |
kmp算法
public int KMP(String original, String target,int pos){
int i = pos, j = 1;
int pLen = target.length();
int mLen = original.length();
char[] mChar = original.toCharArray();
/**
* 模式字符串字符数组从第二个元素位置开始存放
*/
char[] pChar = new char[pLen + 1];
System.arraycopy(target.toCharArray(), 0, pChar, 1, pLen);
int next[] = getNextVal(target);
while (i < mLen && j <= pLen) {
if (j == 0 || mChar[i] == pChar[j]) {
i++;
j++;
} else {
j = next[j];// 匹配失败,回溯
}
}
/**
* 主字符串中存在模式模式字符串
*/
if (j > pLen) {
/**
* 返回模式字符串在主字符串中出现的位置
*/
return i - pLen;
}
return Bunch.FALSE;
}
性能
O(M+N)
KMP算法的改进
nextval
public static int[] getNextVal(String target) {
int i = 1, j = 0, len = target.length();
char[] pChar = new char[len + 1];
System.arraycopy(target.toCharArray(), 0, pChar, 1, len);
int[] next = new int[len + 1];
while (i < len) {
if (j == 0 || pChar[i] == pChar[j]) {
i++;
j++;
if (pChar[i] != pChar[j]) {
next[i] = j; // 设置回溯值
} else {
next[i] = next[j];// 如果两个字符相等,那么将pChar[j]的回溯值复制给pChar[i]
}
} else {
j = next[j];// 从该位置回溯
}
}
return next;
}
nextval的具体过程
T = “ababaaaba”
j=1,next[1]=0,nextVal[1]=0
j=2,next[2]=1,第一位是a,不相等。所以nextVal[2]=1,维持原值。
j=3,next[3]=1,与第一位相等,所以nextVal[3]=nextVal[1]=0
j=4,next[4] = 2,与第二位"b"相等,nextVal[4]= nextVal[2] = 1$
j=5,next[5]=3,第五个字符与第三个字符a相等,nextVal[5]=nextVal[3]=0
j=6,next[6]=4,第六个字符与第4个字符b不相等,nextVal[6]=4
j=7,next[7]=2,第七个字符与第2个字符b不相等,nextVal[7]=2
j=8,next[8]=2,第八个字符与第2个字符b相等,nextVal[8]=nextVal[2]=1
j=9,next[9]=3,第九个字符与第3个字符a相等,nextVal[9]=nextVal[3]=0
所以T串的next[j]如下
j | 1 2 3 4 5 6 7 8 9 |
---|---|
next[j] | 0 1 1 1 2 3 2 2 3 |
nextVal[j] | 0 1 0 1 0 4 2 1 0 |