文学研究助手
题目描述
文学研究人员需要统计某篇英文小说中某些形容词的出现次数和位置。试写一个实现这一目标的文字统计系统,称为“文学研究助手”。
基本要求
- 英文小说存于一个文本文件中。
- 待统计的词汇集合要一次输入完毕,即统计工作必须在程序的一次运行之后就全部完成。
- 程序的输出结果是每个词的出现次数和出现位置所在行的行号,格式自行设计。
测试数据
以你的C源程序模拟英文小说,C语言的保留字集作为待统计的词汇集。
实现提示
约定小说中的词汇一律不跨行。这样,每读入一行,就统计每个词在这行中的出现次数。出现位置所在行的行号可以用链表存储。若某行中出现了不止一次,不必存多个相同的行号。
如果读者希望达到选做部分(1)和(2)所提出的要求,则首先应把KMP算法改写成如下的等价形式,再将它推广到多个模式的情形。
i = 1;
j = 1;
while (i != s.curlen + 1 && j != t.curlen + 1) {
while (j != 0 && s.ch[i] != t.ch[j]) j = next[j];
// j == 0 或 s.ch[i] == t.ch[j]
i++;
}
选作内容
- 模式匹配要基于KMP算法。
- 整个统计过程中只对小说文字扫描一遍以提高效率。
- 假设小说中的每个单词或者从行首开始,或者前置一个空格符。利用单词匹配特点另写一个高效的统计程序,与KMP算法统计程序进行效率比较。
- 推广到更一般的模式集匹配问题,并设待查模式串可以跨行(提示:定义操作GetAChar)。
思路
读取文本文件,逐行处理。每读取一行,就将这行中的词汇进行统计。统计的信息包括词汇的出现次数和出现的行号。这些信息存储在一个map中,map的键是词汇,值是一个结构体Info,其中cnt字段存储词汇的出现次数,ln字段是一个set,存储词汇出现的行号。
在读取每一行时,先将行号增加1,然后移除行中的标点符号,将其替换为空格。然后使用stringstream将行分割为单词,对每个单词,增加其在map中对应的cnt字段,同时将行号添加到ln字段的set中。
最后,遍历map,打印每个词汇的出现次数和出现的行号。
算法分析
时间复杂度主要取决于读取文件和处理每行的时间。读取文件的时间复杂度为 O ( n ) O(n) O(n),其中 n n n为文件的行数。处理每行的时间复杂度为 O ( m ) O(m) O(m),其中 m m m为行的长度。因此,总的时间复杂度为 O ( n m ) O(nm) O(nm)。
空间复杂度主要取决于存储词汇信息的map的大小。在最坏的情况下,每个词汇都不同,因此map的大小为词汇的总数。因此,空间复杂度为 O ( k ) O(k) O(k),其中 k k k为词汇的总数。
AC代码
#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#define AUTHOR "HEX9CF"
using namespace std;
using Status = int;
using ElemType = int;
const int N = 1e6 + 7;
const int TRUE = 1;
const int FALSE = 0;
const int OK = 1;
const int ERROR = 0;
const int INFEASIBLE = -1;
// const int OVERFLOW = -2;
struct Info {
int cnt;
set<int> ln;
};
int main() {
string line;
int lineNumber = 0;
map<string, Info> stats;
ifstream ifs;
ifs.open("text.txt");
if (!ifs) {
cout << "无法打开文件" << endl;
return 1;
}
while (getline(ifs, line)) {
string word;
stringstream ss;
lineNumber++;
// cout << lineNumber << ": ";
// 移除标点符号
replace(line.begin(), line.end(), '.', ' ');
replace(line.begin(), line.end(), ',', ' ');
ss.str(line);
while (ss >> word) {
// cout << word << " ";
stats[word].cnt++;
stats[word].ln.insert(lineNumber);
}
cout << endl;
}
for (const auto i : stats) {
cout << i.first << ": " << endl;
cout << "出现次数:" << i.second.cnt << endl;
cout << "出现位置:";
for (const auto j : i.second.ln) {
cout << j << " ";
}
cout << endl;
}
ifs.close();
return 0;
}