文章目录
一、问题的概述、分析
1.问题的概述
1)实验任务:
- 建立一个文本文件,统计给定单词在文本文件中出现的总次数及位置。
2)实现要求:
- ·文本文件中每个单词不包含空格且不跨行,单词由字符序列构成且区分大小写,统计给定单词在文本文件中出现的总次数,检索输出的某个单词出现在文本中的行号、在该行中出现的位置;
- 设计数据量大的文本,进行子串的查询处理,分析算法运行的时间效率,对所有输出的匹配位置结果进行验证,以证明算法设计和实现的正确性;
- 用朴素模式匹配算法或KMP算法实现字符串定位;
- 可正确读取,保存文本。
2.问题的分析
- 建立一个文本,统计某个单词在该文件中出现的总次数,并进行计数;
- 检索某个特定的单词在文本中出现的行号和位置;
- 字符串定位,学习并运用朴素模式匹配算法或KMP算法。
二、开发工具及编程语言
1.开发工具
IntelliJ IDEA
2.编程语言
Java语言
三、算法分析
1.朴素模式匹配算法(BF算法)
- 其基本思想是用p中的每一个字符去与t中的字符一一比较;
- 如果匹配成功,则返回p在t中首次出现的起始位置; 如果匹配不成功则返回-1;
- 最坏情况比较次数可达:(n-m+1)*m次;
- 虽然这中算法比较简单,但是时间效率不高
2.KMP算法
-
在匹配匹配过程中发生失配时,并不简单的从原始串下一个字符开始重新匹配,而是根据一些匹配过程中得到的信息跳过不必要的匹配,从而达到一个较高的匹配效率。
-
例如,原始串S=abcabcabdabba,模式串为abcabd,当第一次匹配到T[5]!=S[5]时,KMP算法并不将T的下表回溯到0,而是回溯到2,S下标继续从S[5]开始匹配,直到匹配完成。
-
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
3.next数组
1)next数组的定义:
- 设模式串T[0,m-1],(长度为m),那么next[i]表示既是是串T[0,i-1]的后缀又是串T[0,i-1]的前缀的串最长长度(不妨叫做前后缀),注意这里的前缀和后缀不包括串T[0,i-1]本身。
- 如上面的例子,T=abcabd,那么next[5]表示既是abcab的前缀又是abcab的后缀的串的最长长度,显然应该是2,即串ab。注意到前面的例子中,当发生失配时T回溯到下表2,和next[5]数组是一致的,这当然不是个巧合,事实上,KMP算法就是通过next数组来计算发生失配时模式串应该回溯到的位置。
2)next数组的计算:
- 设模式串T[0,m-1],长度为m,由next数组的定义,可知next[0]=next[1]=0,(因为这里的串的后缀,前缀不包括该串本身)。
- 接下来,假设我们从左到右依次计算next数组,在某一时刻,已经得到了next[0]~next[i],现在要计算next[i+1],设j=next[i],由于知道了next[i],所以我们知道T[0,j-1]=T[i-j,i-1],现在比较T[j]和T[i],如果相等,由next数组的定义,可以直接得出next[i+1]=j+1。
- 如果不相等,那么我们知道next[i+1]<j+1,所以要将j减小到一个合适的位置po,使得po满足:
1)T[0,po-1]=T[i-po,i-1]。
2)T[po]=T[i]。
3)po是满足条件(1),(2)的最大值。
4)0<=po<j(显然成立)。
3.参考资料
四、源代码
读取文本
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;
public class Match {
String textWord;
String searchWord;
Scanner scanner =new Scanner(System.in);
/**
*读文本文档
*/
public void readText(){
//防止异常
try{
StringBuffer buffer = new StringBuffer();
BufferedReader bufferedReader= new BufferedReader(new FileReader("D:\\数据结构实践\\题目3\\文本文件.txt"));
String readLine = null;
while( (readLine = bufferedReader.readLine()) != null ){
//设置数据文件的搜索路径
buffer.append(readLine);
}
//转换为字符串型
textWord = buffer.toString();
}catch (IOException ex){
System.out.println(ex.getMessage());
}
}
KMP算法
public int KMP(String textWord, String searchWord) {
int num = 0;
int i = 0;
while( i< textWord.length()) {
//字符型数组
char[] charTextWord = textWord.toCharArray();
char[] charSearchWord = searchWord.toCharArray();
// 模式串的位置
int j = 0;
int[] next = next(searchWord);
while (i < charTextWord.length && j < charSearchWord.length) {
if (j == -1 || charTextWord[i] == charSearchWord[j]) {
// 当j为-1时,要移动i
i++;
j++;
} else {
// j回溯到指定位置
j = next[j];
}
}
if (j == charSearchWord.length) {
site(i-j,searchWord);
num++;
}
}
return num;
}
next数组
public static int[] next(String searchWord) {
char[] charSearchWord = searchWord.toCharArray();
int[] next = new int[charSearchWord.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < charSearchWord.length - 1) {
if (k == -1 || charSearchWord[j] == charSearchWord[k]) {
if (charSearchWord[++j] == charSearchWord[++k]) {
// 当两个字符相等时要跳过
next[j] = next[k];
} else {
next[j] = k;
}
} else {
k = next[k];
}
}
return next;
}
匹配过程
/**
*单词匹配过程
*/
public void start(){
//寻找到的单词的计数器
int count;
//调用读文件
readText();
System.out.println("请输入要查询的单词");
searchWord = scanner.next();
//调用KMP方法
count = KMP(textWord, searchWord);
if(count==0){
System.out.println("未找到单词");
}
else{
System.out.println("一共找到"+count+"个");
}
}
获取位置
public void site(int firstLength, String word){
int allLength = firstLength+word.length()-1;
System.out.println("单词位置:"+firstLength+"~"+allLength);
}
Main函数
public class Main {
public static void main(String[]args){
Match input=new Match();
input.start();
}
}
五、总结
- 用Java读取文件还是比较有挑战的,感觉这个点还需要花更多的时间进行巩固;
- 朴素模式匹配算法比较简单易懂易写,但是它的效率不是很高,相反KMP算法就比较高效,但是它有比较复杂,但综合时间复杂度等因素而言,选KMP算法来匹配会比较优选;
- 在KMP算法学习中花费的时间不太多,只是基本对这个算法的基本概念有所了解,用来编写题目时仍然存在一定的阻碍,所以对于KMP算法还需要进一步的系统学习