一、题目描述
文学研究助手
【问题描述】
文学研究人员需要统计某篇英文小说中某些形容词的出现次数和位置。试写一个实现这一目标的文字统计系统,称为"文学研究助手"。
【基本要求】
英文小说存于一个文本文件中。待统计的词汇集合要一次输入完毕,即统计工作必须在程序的一次运行之后就全部完成。程序的输出结果是每个词的出现次数和出现位置所在行的行号,格式自行设计。
【测试数据】
以你的C源程序模拟英文小说,C语言的保留字集作为待统计的词汇集。
【实现提示】
约定小说中的词汇一律不跨行。这样,每读入一行,就统计每个词在这行中的出现次数。出现位置所在行的行号可以用链表存储。若某行中出现了不止一次,不必存多个相同的行号。
如果读者希望达到选做部分(1)和(2)所提出的要求,则首先应把KMP算法改写成如下的等价形式,再将它推广到多个模式的情形。
i=1;j=1;
while(i!=s.curlen+1&&j!=t.curlerl十1)
{
while(j!=0&&s.ch[i]!=t.ch[j])
j=next[j]; //j==O或s.ch[i]==t.ch[j]
j++;i++;//每次进入循环体,i只增加一次
}
【选作内容】
(1)模式匹配要基于KMP算法(需查阅资料)。
(2)整个统计过程中只对小说文字扫描一遍以提高效率。
二、实验报告
1.1、 设计任务与目标
1.1、1. 设计任务
文学研究人员需要统计某篇英文小说中某些形容词的出现次数和位置。试写一个实现这一目标的文字统计系统,称为"文学研究助手"。
英文小说存于一个文本文件中。待统计的词汇集合要一次输入完毕,即统计工作必须在程序的一次运行之后就全部完成。程序的输出结果是每个词的出现次数和出现位置所在行的行号,格式自行设计。
1.1、2. 目的意义
通过本次实验来加深对KMP算法的认识,巩固数据结构知识。
1.1、3. 具体目标
英文小说存于一个文本文件中。待统计的词汇集合要一次输入完毕,即统计工作必须在程序的一次运行之后就全部完成。程序的输出结果是每个词的出现次数和出现位置所在行的行号,格式自行设计。
(1)模式匹配要基于KMP算法(需查阅资料)。
(2)整个统计过程中只对小说文字扫描一遍以提高效率。
1.2、 数据结构和算法说明
1.2、1. 算法说明:
KMP算法一种改进的模式匹配算法,是D.E.Knuth、V.R.Pratt、J.H.Morris于1977年联合发表,KMP算法又称克努特-莫里斯-普拉特操作。它的改进在于:每当从某个起始位置开始一趟比较后,在匹配过程中出现失配,不回溯i,而是利用已经得到的部分匹配结果,将一种假想的位置定位“指针”在模式上向右滑动尽可能远的一段距离到某个位置后,继续按规则进行下一次的比较。
1.2、2. 设计
先实现键盘输入文档内同,保存到后台的文本中,让用户输入所需要查询的单词,把单词保存到链表中,开始搜索时,从文档中每次读取一整行内容,再对所有的单词进行一个kmp搜索,如查找成功,便把行号与列号保存的链表的数组中,读完文档后,对链表进行遍历,文本只扫描一遍即可把所有对应的查找字读取完成,并结合KMP算法,大大提高了程序的速度。采用模块化设计,程序简洁。
1.2、3. 出现的问题
最初设计的时候,KMP算法的实现原理理解不够透彻,想走捷径,直接套入算法代码,每次都失败,做了许多无用功。最后下定决心,认真研究攻克KMP算法的实现方法。同时,设计初时对指针的使用不够灵活,程序臃肿且效率严重地下,逻辑混乱。花了很多时间重新构思采用什么数据结构才是最合适的,多次推敲后,得出思路,其实就是单链表即可。
1.3、 全部源程序清单
想看全部源码的可以自行下载,资源免费,无须积分或者C币,代码注释多且详细,值得学习,欢迎下载,欢迎学术交流。
全部源码下载
关键代码讲解
typedef struct Node
{
char word[100]; // 每个单词的具体内容
int row = 0; // 行
int total = 0; // 共出现的次数
int col_count[1024]; // 计算文本每行出现的次数
int col_test[1024][100]; // 保存每行中的具体的列数数值
struct Node* next; // 链表指针
}Words;
// 创建链表
Words* createList(int word_number) {
Words* head = new Words; // 头节点 new是存在堆里面的 一般不用于存储数据
Words* pre = head;
for (int i = 0; i < word_number; i++) {
Words* p = new Words;
strcpy(p->word, pattern[i]);
pre->next = p;
pre = p;
p->next = NULL;
}
return head;
}
// 遍历链表信息
void display(Words* head) {
// col_test[][]是保存行数的具体数值
// col_count[] 是保存共出现的次数
Words* p = head->next;
while (p != NULL)
{
int i = 0; // 用于遍历文章的行数
bool have_tip = false;
bool exist = false;
cout << "********************************************" << endl;
cout << "* 目标词 \" " << p->word << "\"\t*";
while (i < p->row) {
int j = 0; // 用于遍历文章的列数
if (p->col_count[i] > 0) {
if (!have_tip) {
cout << "* 共出现了 " << p->total << "次\t*" << endl;
have_tip = true;
}
cout << "* 位于:" << setw(3) << i + 1 << " 行, 第 ";
while (j < p->col_count[i]) { // 当j 小于单词出现的过的次数时
cout << setw(3) << (p->col_test[i][j] + 1);
j++;
exist = true;
}
cout << " 列 \t*" << endl;
i++;
}
else {
i++;
}
}
if (!exist) { // 当不存在的时候
cout << "\t不在文本中! *" << endl;
}
cout << "********************************************" << endl;
p = p->next;
}
}
//要比较的文字叫做pattern,prefix是数组表,n 是长度
void prefix_table(char pattern[], int prefix[], int n) {
prefix[0] = -1;
int len = -1; // 数组的值
int i = 0; //检测的第i个字母
while (i < n) {
if ((len == -1) || (pattern[i] == pattern[len])) {
len++; i++;
prefix[i] = len;
}
else {
len = prefix[len];
}
}
}
// 执行查找 index是指第index个单词 row是行数
void kmp_search(char text[], char pattern[], Words* head, int index, int row) {
Words* p = head;
int col_count = 0;
for (int i = 0; i <= index; i++) { // 找到对应单词的节点
p = p->next;
}
int i = 0, j = 0;
int n = strlen(pattern); //获取待搜索的文字长度
int m = strlen(text); //文本总长度
int* prefix = new int[n];
prefix_table(pattern, prefix, n);
int k = 0;
while (i < m && j < n) {
if (j == n - 1 && text[i] == pattern[j]) { // 查找成功
p->col_test[row - 1][k++] = i - j; // 保存行数具体数值
col_count++;
j = prefix[j]; // 继续往后执行
}
// 匹配中
if (text[i] == pattern[j] || j == -1) {
i++;
j++;
}
else { //不相等的话,j 等于对应表的调整数
j = prefix[j];
}
}
p->col_count[row - 1] = col_count; // 保存每行的匹配到的总数
p->total += col_count;
p->row = row; // 更新行数
}
// 执行全部操作
void action(char* text, int& word_number) {
ifstream ifs; // 文本输入输出流
bool keyboard_input = true; // 控制键盘输入
bool Option = true; // 判断是否继续程序
bool have_tip = false;
while (Option) {
cout << "请输入查找词的数量:" << endl;
while (keyboard_input) { //只要不输入整数,就不会停止要用户输入
if (cin >> word_number) {
keyboard_input = false; //输入了正确的字符,停止键盘输入
}
else { //当输入的不是数字的时候
if (!have_tip) {
cout << "输入有误!请输入数字:" << endl;
have_tip = true;
}
cin.clear(); //修复输入流
cin.ignore(); //取走刚才流中的字符
/*作用是清空输入的错误信息,先改变cin的状态指向标
再删除了输入流刚才传入的错误输入
*/
}
}
input_word(word_number); //输入查找词
ifs.open(FILENAME, ios::in);
Words* head = createList(word_number); // 创建单链表
int row = 0;
while (ifs.getline(text, 100)) { // 每次读一行
row++; // 读入后,行数加一
for (int i = 0; i < word_number; i++) {
kmp_search(text, pattern[i], head, i, row); // 每个单词搜索一次
}
}
ifs.close(); // 关闭文本读入
display(head); // 输出查询结果
Option = ExitSystem(keyboard_input);
clear(head);
delete head; // 删除头结点
}
}
1.4、 程序运行、测试与分析
1.4、1. 测试
以此程序源码进行测试,C++保留字作为查找词
测试结果:功能都能正常执行,能统计出现的次数并把所有的关键词都找出来,当单词不存在也可提示不存在。