一、实验内容
- 对于一篇给定的英文文章,利用线性表和二叉排序树来实现单词频率的统计,实现低频词的过滤,并比较两种方法的效率。具体要求如下:
- 读取英文文章文件(Infile.txt),识别其中的单词。
- 分别利用线性表和二叉排序树构建单词的存储结构。当识别出一个单词后,若线性表或者二叉排序树中没有该单词,则在适当的位置上添加该单词;若该单词已经被识别,则增加其出现的频率。
- 统计结束后,删除出现频率低于五次的单词,并显示该单词和其出现频率。
- 其余单词及其出现频率按照从高到低的次序输出到文件中(Outfile.txt),同时输出用两种方法完成该工作所用的时间。
- 计算查找表的ASL值,分析比较两种方法的效率。
- 系统运行后主菜单如下:
当选择1后进入以下界面:
其中选择2时显示利用线性表来实现所有功能所用的时间。
当在主菜单选择2二叉排序树后,进入的界面与上图类同。
二、实验过程
存储结构的定义:
二叉排序树的存储表示
//二叉排序树的存储表示
typedef struct BSTNode
{
string WordName; //单词名称
int count; //单词出现频率
struct BSTNode *next;
} BSTNode, *BSTree;
线性表的存储表示:
//线性表的存储表示
typedef struct List
{
string WordName; //单词名称
int count; //单词出现频率
struct BSTNode *next;
} List;
平衡二叉树的建立:
平衡二叉树的建立需要不断调整二叉排序树,使二叉树的左右子树的高度之差的绝对值不超过1,即,这种平衡二叉树的查找是比链表快很多的。时间复杂度为O(log2N)。
对于对于查找操作而言,二叉搜索树的时间复杂度介于O(log2N)到O(n)之间,如果退化成单链表,时间复杂度就是顺序查找,为O(n)。如果是平衡二叉树,查找效率会提高到O(log2N)。但是对于平衡二叉树每次的增删都要对每个节点的左右子树进行平衡的判断。链表和二叉树一个是前面建立简单后面查找麻烦,一个是前面建立麻烦,后面查找方便。而ASL(平均查找长度)对于大的数据量,还是用平衡二叉树比较好,ASL远远小于链表。
三、实验代码
low.h
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
using namespace std;
#define maxqsize 1000
typedef struct vnode {
char word[20];
int count;
vnode* next;
}*linklist;
typedef struct tree {
char word[20];
int count;
tree *lcchild, *rcchild;
}*Bitree, *queueelem;
typedef struct queue {
queueelem *data;
int rear, front, tag;
}*Q;
void inordertraverse(Bitree r, FILE* fp)
{
if (r == NULL)return;
inordertraverse(r->lcchild, fp);
fprintf(fp, "%-16s%3d\n", r->word, r->count);
inordertraverse(r->rcchild, fp);
}
void initqueue(queue &q) {
q.data = (queueelem *)malloc(maxqsize * sizeof(queueelem));
q.tag = q.front = q.rear = 0;
return;
}
void enqueue(queue &q, queueelem e)
{
if (q.tag == 1)return;
q.data[q.rear] = e;
q.rear = (q.rear + 1) % maxqsize;
if (q.rear == q.front)q.tag = 1;
else q.tag = 2;
}
void dequeue(queue &q, queueelem &e)
{
if (q.tag == 0)return;
e = q.data[q.front];
q.front = (q.front + 1) % maxqsize;
if (q.rear == q.front)q.tag = 0;
else q.tag = 2;
}
linklist head;
Bitree root;
void Content1()
{
cout<<"-----------------------------\n\n";
cout<<"1:连续执行完毕并显示执行时间\n";
cout<<"2:单步执行\n";
cout<<"3:返回主菜单\n";
cout<<"-----------------------------\n";
}
void Content2()
{
cout<<"-----------------------------\n\n";
cout<<"1:识别并统计单词\n";
cout<<"2:删除低频单词并输出\n";
cout<<"3:输出其余单词及其频率\n";
cout<<"4:计算ASL值\n";
cout<<"5:返回上一级菜单\n";
cout<<"-----------------------------\n";
}
void LinearSortByCharacter()
{
linklist p, q, r;
q = head->next->next;
head->next->next = NULL;
while (q)
{
r = q; q = q->next;
for (p = head; p->next; p = p->next)
if (strcmp(r->word, p->next->word) < 0)
break;
r->next = p->next; p->next = r;
}
}
void LinearReadWord()
{
FILE* fp = fopen("Infile.txt", "r");
if (fp)
cout<<"文件读取成功\n";
char ch[35], str[16];
linklist p, q;
int i, j;
head = (linklist)malloc(sizeof(vnode));
head->next = NULL;
while (~fscanf(fp, "%s", ch))
{
j = 0;
for (i = 0;; i++)
if (isalpha(ch[i]))
str[j++] = ch[i];
else if (j)
{
str[j] = 0;
for (j = 0; str[j]; j++)
if (isupper(str[j]))
str[j] += 32;
for (p = head; p->next; p = p->next)
if (strcmp(str, p->next->word) == 0)break;
if (p->next)
p->next->count++;
else {
q = (linklist)malloc(sizeof(vnode));
strcpy(q->word, str);
q->count = 1;
p->next = q; q->next = NULL;
}
j = 0;
if (!ch[i])break;
}
else if (!ch[i])
break;
}
fclose(fp);
}
void LinearDeleteWord()
{
linklist p = head, q;
if (!p) {
cout<<"尚未读入单词,请先读取文件\n";
return;
}
while (p->next)
if (p->next->count<5)
{
q = p->next; p->next = q->next;
cout<<"删除单词:%s, %d\n", q->word, q->count;
free(q);
}
else p = p->next;
printf("删除完成\n");
}
void LinearPrintWord()
{
linklist p;
if (!head) {
cout<<"尚未读入单词,请先读取文件\n";
return;
}
if (head->next == NULL) {
cout<<"单词已被完全删除,无剩余单词\n";
return;
}
LinearSortByCharacter();
p = head->next;
FILE* fp = fopen("outfile1.txt", "w");
while (p)
{
fprintf(fp, " %-16s%3d\n", p->word, p->count);
p = p->next;
}
if (fp)printf("文件写入成功\n");
fclose(fp);
}
void LinearCalculateASL()
{
linklist p = head;
float asl;
int sn = 0, wn = 0, count = 0;
if (!p) {
cout<<"尚未读入单词,请先读取文件\n";
system("pause");
return;
}
while (p->next)
{
count += p->next->count;
wn++;
sn += p->next->count * wn;
p = p->next;
}
asl = sn * 1.0 / count;
printf("单词总数为: %3d 不同单词 %d 个\n", count, wn);
printf("ASL值为:%.3f\n", asl);
system("pause");
}
void LinearTable1()
{
time_t now = clock();
LinearReadWord();
LinearDeleteWord();
LinearPrintWord();
time_t f = clock();
FILE *fp = fopen("outfile1.txt", "a");
fprintf(fp, "执行时间为%.3fs\n", (double)(f - now) / CLOCKS_PER_SEC);
fclose(fp);
printf("执行时间为%.3fs\n", (double)(f - now) / CLOCKS_PER_SEC);
system("pause");
}
void LinearTable2()
{
int i;
while (true)
{
system("cls");
cout<<"----------------------- 线性表 ------------------------\n\n";
Content2();
while (scanf("%d", &i), i<1 && i>5);
switch (i)
{
case 1:LinearReadWord(); system("pause"); break;
case 2:LinearDeleteWord(); system("pause"); break;
case 3:LinearPrintWord(); system("pause"); break;
case 4:LinearCalculateASL(); break;
case 5:return;
}
}
}
void LinearTable()
{
system("cls");
int i;
while (true)
{
system("cls");
cout<<"------------------------------ 线性表 -------------------------\n\n";
Content1();
while (scanf("%d", &i), i<1 && i>3);
switch (i)
{
case 1:LinearTable1(); break;
case 2:LinearTable2(); break;
case 3:return;
}
}
}
void BitreeReadWord()
{
FILE* fp = fopen("Infile.txt", "r");
root = (Bitree)malloc(sizeof(tree));
char ch[35], str[16];
Bitree p, q;
int i, j, flag = 1;
while (~fscanf(fp, "%s", ch))
{
for (i = 0, j = 0;; i++)
if (isalpha(ch[i]))
str[j++] = ch[i];
else if (j)
{
str[j] = 0;
for (j = 0; str[j]; j++)
if (isupper(str[j]))
str[j] += 32;
if (flag)//flag=1时,根结点还没有数值,所以第一步是将第一个字符串插在root节点
{
strcpy(p->word, str);//把str复制到p->word中,复制函数
p->count = 1;//p点此时这个单词的频率置为一
p->lcchild = NULL;
p->rcchild = NULL;//p的左孩子和右孩子都为空
flag = 0;//第一个节点被占用,为0,下一次插入单词的时候
}
else while (true)//下一个单词的插入直接从这里开始
{
int k = strcmp(p->word, str);//将上一个插入的数值与下一个要插入的数值相比较,相同=0,前小于后为-1。
if (k == 0)//插入二叉树的过程(两个数相同的时候)
{
p->count++; break;//p节点的频率加一
}
else if (k<0 && p->rcchild)//后面要插入的值大,则放到root的右孩子中
{
p = p->rcchild;//p指针指向右子树
continue;//结束本次循环
}
else if (k<0)//后面的值大但是右子树不存在
{
q = (Bitree)malloc(sizeof(tree));//创建一个右子树
strcpy(q->word, str);//将这个值放入q指向的节点
q->count = 1;//q节点的频率加一
p->rcchild = q;//把q置为p节点的右孩子
q->lcchild = NULL;//将q的左右孩子置为空
q->rcchild = NULL;
break;//结束整个循环
}
else if (p->lcchild)//当p左子树存在时先要往下移直到p的左子树不存在,在创建新的节点存入单词
{
p = p->lcchild;
continue;
}
else {
q = (Bitree)malloc(sizeof(tree));
strcpy(q->word, str);
q->count = 1;
p->lcchild = q;
q->lcchild = NULL;
q->rcchild = NULL; break;
}
}
j = 0;
if (!ch[i])break;//当字符串读取完时,跳出for循环
}
else if (!ch[i])break;//当整个文件读取完后,跳出while循环
}
if (fp)//文件打开成功
printf("文件读取成功\n");
fclose(fp);
}
void BitreeFilter(int k)//二叉树的过滤低频词
{
queue Q;//创建一个队列Q
Bitree p, r;//声明
initqueue(Q);//初始化操作,建立一个空队列Q
r = root;
enqueue(Q, r);//若队列Q存在,插入新元素r到到队列Q中成为队尾元素
while (true)
{
dequeue(Q, p);//过滤一遍所有的词,都入队一遍再出队,找一个满足条件的节点作为根结点
if (p->lcchild)//如果p的左孩子存在,将p的左孩子插入Q的尾部
enqueue(Q, p->lcchild);
if (p->rcchild)
enqueue(Q, p->rcchild);
if (p->count >= k)break;//如果频率大于5,退出
else {
printf("删除单词:%s, %d\n", p->word, p->count);
free(p);
}
}
p->lcchild = NULL; p->rcchild = NULL;
root = p;
while (Q.tag)//当队不为空时
{
r = root;
dequeue(Q, p);
if (p->lcchild)enqueue(Q, p->lcchild);
if (p->rcchild)enqueue(Q, p->rcchild);
p->lcchild = NULL; p->rcchild = NULL;
if (p->count >= k)//当频率大于等于5时
while (true)
{
if (strcmp(p->word, r->word) > 0 && r->rcchild)//根结点的单词大于p节点的单词
//if (p->count<r->count&&r->rcchild){
{
r = r->rcchild; continue;
}
else if (strcmp(p->word, r->word) > 0)
//else if (p->count<r->count){
{
r->rcchild = p; break;
}
else if (strcmp(p->word, r->word) < 0 && r->lcchild)
{
//if (p->count >= r->count&&r->lcchild){
r = r->lcchild; continue;
}
else
r->lcchild = p; break;
}
else {
printf("删除单词:%s, %d\n", p->word, p->count);
free(p);
}
}
}
void BitreeDeleteWord()//二叉树删除词汇
{
if (!root) {
cout<<"尚未读入单词,请先读取文件\n";
return;
}
BitreeFilter(5);
cout<<"删除完成\n";
}
void BitreePrintWord()//二叉树打印词汇
{
Bitree p = root;
if (!p) {
cout<<"尚未读入单词,请先读取文件\n";
return;
}
FILE* fp = fopen("outfile2.txt", "w");//建立文件写入单词
inordertraverse(root, fp);//从根结点开始遍历
if (fp)printf("文件写入成功\n");
fclose(fp);
}
int CalcuSearchTime(Bitree bi, int level)//计算总的查找次数递归调用
{
int left = 0;
int right = 0;
if (bi->lcchild)
left = CalcuSearchTime(bi->lcchild, level + 1);
if (bi->rcchild) {
right = CalcuSearchTime(bi->rcchild, level + 1);
}
return bi->count * level + left + right;//次数乘以乘数加上左孩子和右孩子
}
int CalcuWordNum(Bitree bi)//计算词汇总数
{
int left = 0;
int right = 0;
if (bi->lcchild)
left = CalcuWordNum(bi->lcchild);
if (bi->rcchild) {
right = CalcuWordNum(bi->rcchild);
}
return bi->count + left + right;//左子树加右子树再加上每个节点的频率
}
int CalcuNode(Bitree bi)//计算节点数
{
int left = 0;
int right = 0;
if (bi->lcchild)
left = CalcuNode(bi->lcchild);
if (bi->rcchild) {
right = CalcuNode(bi->rcchild);
}
return 1 + left + right;//第一个根节点加上左孩子和右孩子的个数
}
void BitreeCalcuASL()//计算二叉树ASL
{
Bitree p = root;
if (!p) {
cout<<"尚未读入单词,请先读取文件\n";
system("pause");
return;
}
int wordNum = CalcuWordNum(p);
int NodeNum = CalcuNode(p);
int SearchTime = CalcuSearchTime(p, 1);
printf("单词总数为: %3d 不同单词 %d 个\n", wordNum, NodeNum);
printf("ASL值为:%.3f\n", SearchTime * 1.0 / wordNum);//asl为查找所有单词的次数除以单词的总个数
system("pause");
}
void BitreeSort1()//二叉树连续执行
{
time_t now = clock();
BitreeReadWord();
BitreeDeleteWord();
BitreePrintWord();
time_t f = clock();
FILE *fp = fopen("outfile2.txt", "a");//写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
fprintf(fp, "执行时间为%.3fs\n", (double)(f - now) / CLOCKS_PER_SEC);//写入文件中
fclose(fp);
printf("执行时间为%.3fs\n", (double)(f - now) / CLOCKS_PER_SEC);//CLOCKS_PER_SEC为将clock()函数的结果转化为以秒为单位的量,写入外面
system("pause");
}
void BitreeSort2()//二叉树单步执行
{
int i;
while (true)
{
system("cls");
cout<<"------------------- 二叉排序树 --------------------------\n\n";
Content2();
while (scanf("%d", &i), i<1 && i>5);//循环直到得到有效值跳出
switch (i)
{
case 1:BitreeReadWord(); system("pause"); break;
case 2:BitreeDeleteWord(); system("pause"); break;
case 3:BitreePrintWord(); system("pause"); break;
case 4:BitreeCalcuASL(); break;
case 5:return;
}
}
}
void BitreeSort()//二叉树目录
{
system("cls");
int i;
while (true)
{
system("cls");
cout<<"------------------- 二叉排序树 --------------------------\n\n";
Content1();
while (scanf("%d", &i), i<1 && i>3);
switch (i)
{
case 1:BitreeSort1(); break;
case 2:BitreeSort2(); break;
case 3:return;
}
}
}
void start()
{
int i;
while (true)
{
system("cls");
cout<<"------------------------------\n";
cout<<"1:线性表\n";
cout<<"2:二叉排序树\n";
cout<<"3:退出\n";
cout<<"请输入所要选择的选项,按数字(1-3)\n";
while (scanf("%d", &i), i<1 && i>3);
switch (i)
{
case 1:LinearTable(); break;
case 2:BitreeSort(); break;
case 3:return;
}
}
}
mian.cpp
int main()
{
system("title 低频词过滤系统");
start();
}