蓝桥杯算法基础(29)字符串匹配(RabinKarp)(KMP)(前缀树,字典树,trie,后缀数组,高度数组)

 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;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/769837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

createDocumentFragment()用法总结

createDocumentFragment()用法总结 1.描述 DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。 因为文档片段存在于内存中,并不在DOM树中,…

hcia datacom课程学习(3):http与https、FTP

1.超文本传输协议&#xff1a;http与https &#xff08;1&#xff09;用来访问www万维网。 wwwhttp&#xff0b;html&#xff0b;URLweb &#xff08;2&#xff09;它们提供了一种发布和接受html界面的方法&#xff1a;当在网页输入URL后&#xff0c;从服务器获取html文件来…

Java项目基于Docker打包发布

一、后端项目 1.打包应用 mvn clean package -DskipTests 2、新建dockerfile文件 #基础镜像 FROM openjdk:8 #工作空间 WORKDIR /opt #复制文件 COPY wms-app-1.0-SNAPSHOT.jar app.jar&#xff08;add也可以&#xff09; #配置容器暴漏的端口 EXPOSE 8080 //不暴露端口使用…

vue的常用指令

v-bind&#xff1a;用于响应地更新 HTML 属性。 <img v-bind:src"imageSrc"> <!-- 简写形式 --> <img :src"imageSrc"> v-on&#xff1a;用于监听 DOM 事件&#xff0c;并在触发时运行一些 JavaScript 代码。 <button v-on:cli…

c语言函数大全(Q开头)

c语言函数大全(Q开头) There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave quickly. 函数名…

软件测试|Python random模块,超乎想象的强大

Python的random模块是一个非常强大的工具&#xff0c;用于生成随机数和随机选择。它提供了许多函数和方法&#xff0c;可以满足各种随机化需求。本文将介绍random模块的基本功能和常见用法&#xff0c;以帮助读者更好地理解和利用这个模块。 返回整数 random.randange() 语法…

淘宝店商家爬虫工具 天猫店卖家电话采集软件使用指南

淘宝店商家爬虫工具是一款用于采集天猫店卖家电话号码的软件。本文将提供使用指南&#xff0c;并附带相关代码&#xff0c;帮助用户快速了解和使用该软件。 代码示例&#xff1a; import requests from bs4 import BeautifulSoup# 设置请求头 headers {User-Agent: Mozilla/…

关于 FastAPI 路径参数,你知道多少?

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

建立远程 Git 代码仓库

一、建立远程代码库 要在 Git 中建立远程代码库&#xff0c;你通常需要在代码托管平台上创建一个新的远程仓库&#xff0c;然后将本地仓库与之关联。以下是一般步骤&#xff1a; 在代码托管平台上创建远程仓库&#xff1a; 登录到你选择的代码托管平台&#xff08;如 GitHub、…

React Developer Tools安装

问题描述 在react开发中&#xff0c;需要插件来帮助我们开发&#xff0c;例如&#xff1a; 方法 &#xff08;可能需要魔法 进去后搜索&#xff1a; 点击下载即可

【Nebula笔记】基础操作

目录 一、预备~ 二、基础操作 (一) 图空间 1. 创建图空间 2. 清空图空间 3. 其他 4. FAQ 执行DROP SPACE语句删除图空间后&#xff0c;为什么磁盘的大小没变化&#xff1f; (二) 点类型 1. 创建Tag 2. 删除Tag 3. 更新Tag 4. 其他 (三) 边类型 1. 创建Edge type…

git如何在某个commitId的状态提交到一个分支

有些时候&#xff0c;我们在使用子仓库&#xff0c;或者其他情况&#xff0c;会有一个状态是当前的git仓库是在一个commitId上&#xff0c;而没有在一个分支上&#xff1a; 这时如果想要把基于这个commitId创建一个分支&#xff0c;可以使用下面这个命令&#xff1a; git push…

HCIA实验

实验目的&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址&#xff0c;之后不能再对其进行任何配置&#xff1b; 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配&#xff1b; 3、R1、R2、R4&#x…

前端理论总结(html5)——form表单的新增特性/h5的新特性

form表单的新增特性 range&#xff1a;范围 color&#xff1a;取色器 url&#xff1a;对url进行验证 tel&#xff1a;对手机号格式验证 email&#xff1a;对邮箱格式验证 novalidate &#xff1a;提交表单时不验证 form 或 input 域 numbe…

i5 1240p和r7 8840HS差距 酷睿i51240p和r7 8840HS参数对比

r7 8840HS采用 Zen 4架构 4 nm制作工艺8核 16线程主频 3.3GHz睿频5.1GHz 三 级缓存16MB TDP 功耗 28w 搭载AMD Radeon 780M核显 选r7 8840HS还是i5-1240P这些点很重要 http://www.adiannao.cn/dy i5-1240P处理器具有4个性能核心&#xff0c;8个效能核心&#xff0c;总计12核心…

JUC-多线程

目录 进程 线程 线程的串行 区别 多线程 进程 是指计算机中已执行的程序&#xff0c;曾经是分时系统的基本运作单位在面向进程设计的系统&#xff08;如早期的UNIX&#xff0c;Linux 2.4及更早的版本&#xff09;中&#xff0c;是程序的基本执行实体在面向线程设计的系统…

【网络建设与运维】2024年河北省职业院校技能大赛中职组“网络建设与运维”赛项规程

培训、环境、资料、考证 公众号&#xff1a;Geek极安云科 网络安全群&#xff1a;775454947 网络系统管理群&#xff1a;223627079 网络建设与运维群&#xff1a;870959784 极安云科专注于技能提升&#xff0c;赋能 2024年广东省高校的技能提升&#xff0c;在培训中我们的应急…

jdbc连接回顾

不使用任何工具类手动连接 package com.oracle.jdbc;import java.sql.*;/***jdbc查询 jdbc数据库下&#xff0c;user表中所有数据并打印在控制台* jdbc操作数据库步骤* 1注册驱动* 2创建数据库连接对象* 3获取传输器对象* 4执行sql* 5处理结果集* 6释放资源*/public cla…

OSCP靶场--Crane

OSCP靶场–Crane 考点(CVE-2022-23940sudo service提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.229.146 -sC -sV --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-25 08:07 EDT Nmap scan report for 192.16…

python环境移植(本机windows到离线windows环境)

Python环境整体迁移(包括无网络情况)_python 迁移 新老无法联网-CSDN博客