RabinKarp
RabinKarp
S:ABABAB m个
P:ABB n个1.朴素算法,挨个匹配
2.哈希法 hash->滚动哈希 c0*31^2+c1*31^1+c2类似于进制的求法求hash值(c0*31+c1)*31+c2
hash(p)=o(n)
hash(s)=o(m*n)private static void match(String p,String s){long hash_p=hash(p);int p_len=p.length;for(int i=0;i+p_len<=s.length;i++){long hash_i=hash(s.subString(i,i+p_len));//i作为起点,长度为length的子串的hash值if(hash_i==hash_p){System.out.println("match:"+i);}}
}final static long seed=31;
static long hash(String str){long hash=0;for(int i=0;i!=str.length;i++){//(c0*31+c1)*31+c2hash=seed*hash+str.charAt(i);}return hash%Long.MAX_VALUE;
}每次求hash中间都相同的字符被计算//n是子串的长度
//用滚动的方法求出s中长度为n的每个字串的hash求出hash数组
final static long seed=31;static long[] hash(final String s,final int n){long[] res=new long[s.length()-n+1];//前n个字符的hashres[0]=hash(s.subString(0,n));for(int i=n;i<s.length;i++){//i的位置为新字符char newChar=s.charAt(i);char oldChar=s.charAt(i-n);//前m个字符的hash*seed-n个字符的第一个字符*d的n次方long v=(res[i-n]*seed)-NExponent.ex2(seed,n)*oldChar+newChar)%Long.MAX_VALUE;;res[i-n+1]=v;}//NExponent.ex2(seed,n)自己封装的工具,求seed的n次方return res;
}static long hash(String str){long h=0;for(int i=0;i!=str.length;i++){h=seed*h+strcharAt(i);}
}
KMP
KMP
1.暴力匹配
private static int indexOf(String s,String p){int i=0;int sc=i;int j=0;while(sc<s.length()){if(s.charAt(sc)==p.charAt(j)){sc++;j++;if(j==p.length)return i;}else{i++;sc=i;//扫描指针以i为起点}}
}2.next数组的含义及应用
后缀与前缀的最长匹配长度
f(L)=k,L失配,s回溯k
不考虑第L个元素
P[0~L-1]的pre, suff最长匹配数
next数组
b a b a b b
-1 0 0 1 2 3
取该位next数组,应将该为元素排除在外
取下标为0的next,为-1,因为将排除在外
取下标为1的next,为0,将a排除在外,b没有前缀没有后缀,因为我们不包含它的整体
前两个next一般都为-1,0
取下标为2的next,为0,排除b,b a前缀后缀匹配为0
取下标为3的next,为1,排除a,b a b前缀后缀匹配为1,前缀b和后缀b匹配
如此取出所有next3.求next数组
private static int indexOf(String s,String p){if(s.length()==0||p.length()==0)return -1;if(p.length()>s.length())return -1;//模式串大于原串的长度int count=0;int[] next=next(p);int i=0;//s位置int j=0;//p位置int sLen=s.length();int pLen=p.length();while(i<sLen){//如果j=-1,或者当前字符串匹配成功,即S[i]==p[i],则令i++,j++//j=-1,因为next[0]=-1,说明p的第一位和i这个位置无法匹配,这时i,j都增加1,i移位,j从0开始if(j==-1||s.charAt(i)==p.charAt(j)){i++;j++;}else{//如果j!=i,且当前字符匹配失败,即S[i]!=P[j],则令i不变,j=next[j]//next[j]即为j所对应的next数组j=next[j];}if(j==plen){//匹配成功了count++;j=next[j];//求匹配成功的次数,让i继续向下走// return (i-j);}}return count;//返回匹配成功的次数//return -1;}//求next数组
j,k为上字符是否相同
如果pj==pk,next[j+1]=k+1或者k<0,next[j+1]=k+1;
否则k继续回溯,直到满足pj==pk或k<0;
//若是有一个相同,则下一个位置即j+1的next为k+1,
//即前缀后缀有一个成功匹配,到达下一个位置,
//再看当前位置+1后j的元素,与+1后的k的相比较,
//若是相同,则j+1位置的next为K+1,则证明有两个成功匹配的长度,
//若是下一个位置j与k位置的元素不同,则之前成功匹配的则作废,
//则k回溯找前面相同的继续前缀后缀匹配,
//若是找不到,即k等于-1,则下一个位置的next为0,相当于-1+1=0
private static int[] next(Strig ps){int pLength=ps.length();char[] p=ps.toCharArray();next[0]=-1;if(ps.length()==1){return next;}next[1]=0;//初始化next[0]=-1,next[1]=0int j=1;int k=next[j];//看看位置j的最长匹配前缀在哪里while(j<plength-1){//现在要推出next[j+1],检查j和k位置上的关系即可if(k<0||p[j]==p[k]){//当k回溯到-1或是p[j]和p[k]相等时next[++j]=++k;}else{k=next[k];//k回溯}}return next;}
前缀树(字典树,Trie);后缀数组
1.什么是后缀数组
就是串的所有后缀子串的按字典序排序后,在数组中记录后缀的起始下标,后缀数组就是:排名和原下标的映射,Sa[0]=5,起始下标为5的后缀在所有后缀中字典序最小rank数组:给定后缀的下标,返回字典序,rk[5]=0;rk[sa[i]]=i2.后缀数组有什么用子串:一定是某个后缀的前缀private static void match(){String s="ABABABABB";String p="BABB";Match03_SuffixArray.Suff[] sa=Match03_SuffxiArray.getSa2(s);//后缀数组int l=0;int r=s.length()-1;//二分查找。nlog(m)while(r>=l){int mid=l+((r-l)>>1);//居中的后缀Match03_SuffixArray.Suff midSuff=Sa[mid];String suffStr=midSuff.str;int compareRes;//将后缀和模式串比较,o(n)if(suffstr.length()>=p.length())compareRes=suffstr.subString(0,p.length()).compareTo(p);//若是某后缀长度大于子串的长度,则截取后缀前面子串长度的部分与子串比较//若是后缀大于子串,则为正,r=mid-1//若是后缀小于子串,则为负,l=mid+1}else{compareRes=suffstr.compareTo(p);//后缀长度小于p,若是相同则p更大,因为字符串更长的更大,若是不同,则按字典序正常比较大小}if(compareRes==0){System.out.println(midSuff.index);break;}else if(compareRes<0){l=mid+1;}else{r=mid-1;}}ABABABABB
ABABABABB-0BABABABB-1ABABABB-2BABABB-3ABABB-4BABB-5ABB-6BB-7B-8ABABABABB-0 ABABABB-2 ABABB-4.....suffix array sa3.怎么求后缀数组3.1 把所有的后缀放入数组,Arrays.sort();nlog(n)//直接对所有后缀数组排序,因为字符串的比较消耗o(n)
//所以整体为n^2*log(n)
public static Suff[] getSa(String src){int strLength=src.length();//sa是排名到下标的映射,即sa[i]=k说明排名为i的后缀是从k开始的Suff[] suffixArray=new Suff[StrLength];for(int i=0;i<strLength;i++){String SuffI=src.substring(i);//substring截取字符串,此字符串为一个子字符串,从索引位置的字符开始,直到字符串末尾suffixArray[i]=new Suff(suffI,i);//将该子字符串与所处位置放入后缀数组}Arrays.sort(suffixArray);//快排return suffixArray;
}//面向对象,类似与c语言结构体
static class Suff implements Comparable<suff>{//Comparable接口的约定,必须实现compareTo()String str;//后缀内容int index;//起始下标public Suff(String str,int index){this.str=str;this.index=index;}public int campareTo(Suff o2){return this.str.compareTo(o2.str);}public String toString(){return "Suff{"+"str'"+str+'\''+",index="+index+"}";}}
倍增法
3.1
1.suff[] A~0,B~1,A~1,B~3,A~4,B~5,A~6,B~7,B~8
2.Sort: A~0,A~2,A~4,B~1,B~3,B~5,B~7,B~8
3.rk r[0]-0,r[2]=1,r[4]=0,r[6]=0,r[1,3,5,7,8]=1给我下标,给你排名1.suff[] AB~0,BA~1,AB~1,BA~3,AB~4,BA~5,AB~6,BB~7,B~8
2.Sort: A~0,A~2,A~4,B~1,B~3,B~5,B~7,B~8
3.rk r[0]-0,r[2]=1,r[4]=0,r[6]=0,r[1,3,5,7,8]=1给我下标,给你排名int[] rk=new int[Strlength];//suffix array
Suff[] suffixArray = new Suff[strlength];for(int k=1;k<=strlength;k*=2){for(int i=0;i<strlength;i++){//k代for(int i=0;i<strlength;i++){//k代表取值范围,一开始只截一个字符参与排序,下次两个字符参与排序,依次乘2}String suffI=src.substring(i,i+k>strlengthl?strelngth:i+k);suffixArray[i] =new Suff(suffI,i);}if(k==1)//一个字符,直接排序Arrays.sort(suffixArray);//nlog(n)else{//填充完毕,开始排序nlog(n)final int kk=k;Arrays.sort(suffixArray,(o1,o2)->{//简化匿名内部类,定义一个比较器//不是基于字符串比较,而是利用之前的rankint i=o1.index;int j=o2.index;if(rk[i]==rk[j]){//如果o1和o2的前半段try{//利用上一级排名给下一级排名//无论是rk[i]还是rk[i+kk/2]在上一级排名中都是有的return rk[i+kk/2]-rk[j+kk/2];//若是有一个后缀字符串较短。+kk/2就会越界//x x//i i+k/2//Y Y//j j+k/2}catch(ArrayIndexOutOfBoundsExcept e){//越界return o1.str.length()-o2.length();//前半部分一致,谁长谁在后}}else{//若不相等,则排名大的在后return rk[i]-rk[j];}});}int r=0;
rk[suffixArray[0].index]=r;
for(int i=1;i<strlen;i++){if(suffixArray[i].compareTo(suffixArray[i-1]==0)){//内容相同rk[suffixArray[i].index]=r;//索引->排名,给定索引,知道单个字符的排名}else{rk[suffixArray[i].index]=++r;//索引->排名,给定索引,知道单个字符的排名}}}return suffixArray;
}
private static void match(){
String s="ABABABABB";
String p="BABB";Match03_SuffixArray.Suff[] sa=Match03_SuffixArray.getSa2(s);//后缀数组
int l=0;
int r=s.length();
//二分查找
while(r>=l){int mid=l+((r-l)>>1);//居中的后缀Match03_SuffixArray.Suff midSuff=sa[mid];String suffstr=midSuff.str;int compareRes;//将后缀和模式串比较o(n)if(suffstr.length()>=p.length()){compareRes=suffix.substring(0,p.length()).compareTo(p);}else{compareRes=suffStr.compareTo(p);//相等了,输出后缀的起始位置}if(compareRes==0){System.out.println(midSuff.index);break;}else if(compareRes<0){l=mid+1;}else{r=mid-1;}
}}3.2倍增法k=1,一个字符,排序,得到sa,rkk=2,利用上一轮的k快速比较两个后缀k=4k=8....public static Suff[] getSa2(String src){int n=src.length();Suff[] sa=new Suff(n);for(int i=0;i<n;i++){sa[i]=new Suff(src.charAt(i),i);//有单个字符,接下来排序}Arrays.sort(sa);
}//rk是下标到排名的映射int[] rk=new int[n];//suffix arrayrk[sa[0].index]=1;for(int i=1;i<n;i++){rk[[sa[i].index]=rk[sa[i-1]].index;if(sa[i].c!=sa[i-1].c)rk[sa[i].index]++;}//倍增法for(int k=2;rk[sa[n-1].index]<n;k*=2){final int kk=k;Arrays.sort(sa,(o1,o2)->{//不是利用字符串比较而是利用之前的rankint i=o1.index;int j=o2.index;if(rk[i]==r[j]){//如果第一关键字相同if(i+kk/2>=n||j+kk/2>=n)return -(i-j);//如果某个后缀不具有第二关键字,那肯定较小,索引靠后的更小return rk[i+kk/2]-rk[j+kk/2];}else{return rk[i]-rk[j];}});//排序,end//更新rankrk[sa[0].index]=1;for(int i=1;i<n;i++){int i1=sa[i].index;int i2=sa[i-1].index;rk[i1]=rk[i2];try{if(!src.subString(i1,i1+kk).equals(src.subString(i2,i2+kk)))rk[i1]++;}catch(Exeption e){rk[i1]++;}}}
高度数组
height[0]=0;
height[i]=Lcp(sa[i],sa[i-1])串 串
//Lcp最长公共前缀0 ABCABC 0 ABC(排序后)
1 BCABC 1 ABCABC
2 CABC 2 BC
3 ABC 3 BCABC
4 BC 4 c
5 c 5 CABC
rk[0]=1 0 ABCABC-->1 ABCABC
sa[1]=0 1 ABCABC-->0 ABCABC
sa[rk[0]]=0
rk[sa[1]]=1
根据排名,求的当前串的位置和前一个串的位置,再根据位置求得两个串,在比对两个串求得最长公共前缀//height[rk(i-1)]->height[rk(i)]
//height[rk(i)]->height[rk(i+1)]
0 ABC 2 BC
1 ABCABC 3 BCABC
就是抹了个帽子A,最长公共前缀-1
// height[rk(i+1)]>=height[rk(i)]-1public static int[] getHeight(String src,Suff sa){//Suff[] Sa=getSa2(src);int strlength=src.length();int[] rk=new int[Strlength];//将rank表示为不重复的排名即0-n-1for(int i=0;i<strLength;i++){rk[Sa[i].index]=i;//index为原始的位置,i为排序后的位置}int[] height=new int[StrLength];//如果已经知道后缀数组中i与i+1的lcp为h,那么i代表的字符串与i+1代表的字符串去掉首字母的lcp为h-1//根据这个我们可以发现,如果知道i与后缀数组中在它后一个的lcp为k,那么它去掉首字母后的字符串与其在后缀数组中的后一个的lcp大于等于k-1//例如对于字符串abcefabc,我们直达票abcefabc与abc的lcp为3//那么bcefabc与bc的lcp大于等于3-1//利用这一点就可以o(n)求出高度数组int k=0;for(int i=0;i<strLength;i++){int rk_i=rk[i];//i后缀的排名if(rk_i==0){height[0]=0;//height[0]默认为0continue;}int rk_i_1=rk_i-1;//前一个位置int j=sa[rk_i_1].index;//j是i串字典序靠前的串的下标if(k>0)k--;//利用height[rk(i+1)]>=height[rk(i)]-1,下一次只是去掉了一个帽子的公共前缀for(;j+k<strLength&&i+k<strLength;k++){if(src.charAt(j+k)!=src.charAt(i+k))break;}height[rk_i]=k;//i是一个个递增的来的,若是这个方法是错误的,应该一个一个比对公共前缀}return height;}