题目如下:
实验任务:建立一个文本文件,统计给定单词在文本文件中出现的总次数及位置;
实现要求:
文本文件中每个单词不包含空格且不跨行,单词由字符序列构成且区分大小写,统计给定单词在文本文件中出现的总次数,检索输出的某个单词出现在文本中的行号、在该行中出现的位置。
设计数据量大的文本,进行子串的查询处理,分析算法运行的时间效率,对所有输出的匹配位置结果进行验证,以证明算法设计和实现的正确性。
用朴素模式匹配算法或KMP算法实现字符串定位;
可正确读取,保存文本;
工具:IDEA jdk 1.8
总结:要求没有完全实现,主要是KMP算法的实现。
讲解:KMP算法很有意思,但变化不多,二分搜索才是老折磨王了。用了两种方法实现,都基于KMP的原始思想。
原始思想如下:利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的.
首先讲第一种思路:参考链接:www.bilibili.com/video/BV1ci4y1F7Es c语言技术网
第一:在匹配的过程中,目标串的指针不需要回溯,只回溯模式串的指针
第二:如果模式串和目标串前n个字符匹配成功,遇到匹配失败的字符时,模式串的指针回溯的位置由模式串的内容决定(回溯到匹配失败位置前的模式串内容
的最长公共前后缀的长度+1),然后继续比较。
这里面的next数组存放了匹配失败位置前的模式串内容的最长公共前后缀的长度+1。
nextval数组存放了next数组优化后的回溯位置。(优化逻辑:如果模式串当前位置字符与next数组相同位置的索引指向的字符相同,则用前面字符索引的nextval
值替换当前索引的nextval值,否则将当前索引的next值赋给当前索引的nextval值)
import java.util.ArrayList;
import java.util.List;
public class KMP2 {//原始思想+优化
private String pat;//模式串
private int[] next;//跳转位置数组
private int[] nextval;//优化跳转位置数组
static List<Integer> list;//记录初始位置及个数的列表
public KMP2(String pat){
this.pat = pat;
int n=pat.length();
next=new int[n];
nextval=new int[n];
list=new ArrayList<>();
getnext();
getnextval();
}
public void getnext(){
int n=pat.length();
if (n==0) return;
if (n==1) {
next[0]=-1;
return;
}
if (n==2) {
next[0]=-1;
next[1]=0;
return;
}
next[0]=-1;
next[1]=0;
for(int i=2;i<n;i++){
int maxlen=0;
for (int j = 1; j <i ; j++) {//比较前后缀是否相同
String s1;
String s2;
s1=pat.substring(0,j);
s2=pat.substring(n-j,n);
if (s1.equals(s2)) maxlen=j;
}
next[i]=maxlen;
}
}
public void getnextval(){
int n=pat.length();
nextval[0]=-1;
for (int i = 1; i <n ; i++) {
if (pat.charAt(i)==pat.charAt(next[i]))
nextval[i]=nextval[nextval[i]];
else
nextval[i]=next[i];
}
}
public void search(String txt) {
int n = txt.length();
int m = pat.length();
int i = 0, j = 0;
while (i < n) {
if (j == -1 || txt.charAt(i) == pat.charAt(j)) {
i++;
j++;
} else {
//j=next[j];
j = next[j];
}
if (j==m){
list.add(i-m);
j=0;
}
}
}
public void print(){
int n=list.size();
System.out.println("出现的总次数:"+n);
for (int i = 0; i <n ; i++) {
System.out.print("出现的起始位置:"+list.get(i)+"\t");
}
}
public static void main(String[] args) {
KMP2 kmp2=new KMP2("ab");
String txt="abhdfhdsihfabduidfghiodfabdjioegoerigabodfger09geoirgab";
kmp2.search(txt);
kmp2.print();
}
}
第二种思路: 参考链接:mp.weixin.qq.com/s/r9pbkMyFyMAvmkf4QnL-1g labuladong的算法小抄
动态规划是穷举加上去掉重叠子问题。难点是状态转移方程。具体自行了解。这里用到了动态规划的思想,结合了原始思想。
首先是状态转移二维数组,行长为模式串的长度,列长为256.(可以自行调整,有多少调多少,ASCII里通常最多为128个字符,写成256是因为二进制有8位)
二维保存当前状态,一维保存字符,值为当前状态遇到字符后转移到的下一个状态。
一共有n(模式串的长度)种状态,从0到n-1,如果状态等于n了就表示匹配成功一个,将其加入列表并将模式串指针置0。
import java.util.ArrayList;
import java.util.List;
public class KMP {//动态规划思想结合原始思想(状态转移)
private int [][]dp;//状态转移数组
private String pat;//模式串(搜索的字符串)
static List<Integer>list;//记录初始位置及个数的列表
public KMP(String pat) {
this.pat = pat;
int m=pat.length();
list=new ArrayList<>();
//dp[状态][字符] = 下个状态
dp=new int[m][256];
//base case
dp[0][pat.charAt(0)]=1;
//前方相同前缀字符位置索引初始化为0
int X=0;
//构建状态转移图
for (int i = 1; i < m; i++) {
for (int c = 0; c <256 ; c++) {
dp[i][c]=dp[X][c];
dp[i][pat.charAt(i)]=i+1;
//更新前方相同前缀字符位置索引
X=dp[X][pat.charAt(i)];
}
}
}
public void search(String txt){
int m=pat.length();
int n=txt.length();
//pat的初始状态为0
int j=0;
for (int i = 0; i <n ; i++) {
//计算pat的下一个状态
j=dp[j][txt.charAt(i)];
//到达终止状态,将起始位置加入列表,继续搜索
if (j==m) {
list.add(i-m+1);
j=0;
}
}
}
public void print(){
int n=list.size();
System.out.println("出现的总次数:"+n);
for (int i = 0; i <n ; i++) {
System.out.print("出现的起始位置:"+list.get(i)+"\t");
}
}
public static void main(String[] args) {
String txt="abhdfhdsihfabduidfghiodfabdjioegoerigabodfger09geoirgab";
KMP kmp=new KMP("ab");
kmp.search(txt);
kmp.print();
}
}