前言
各位在做课程设计的学弟学妹,请保持独立思考的能力!
复制粘贴满足自己的虚荣心,却永远解决不了你是“菜鸟”的事实。
题目描述
许多应用程序,如字处理软件,邮件客户端等,都包含了单词检查特性。单词检查是根据字典,找出输入文本中拼错的单词,我们认为凡是不出现在字典中的单词都是错误单词。不仅如此,一些检查程序还能给出类似拼错单词的修改建议单词。 例如字典由下面几个单词组成:
bake cake main rain vase
如果输入文件中有词vake ,检查程序就能发现其是一个错误的单词,并且给出 bake, cake或vase做为修改建议单词。
修改建议单词可以采用如下生成技术:
(1)在每一个可能位置插入‘a-‘z’中的一者
(2)删除单词中的一个字符
(3)用‘a’-'z’中的一者取代单词中的任一字符
很明显拼写检查程序的核心操作是在字典中查找某个单词,如果字典很大,性能无疑是非常关键的。
你写的程序要求读入字典文件,然后对一个输入文件的单词进行检查,列出其中的错误单词并给出修改建议。
课程设计必须采用如下技术完成并进行性能比较(也就是,同学要提交多份采用不同技术实现的代码,而不仅仅是一份AC的代码)。
(1)朴素的算法,用线性表维护字典
(2)使用AVL树维护字典
(3)采用hash技术维护字典
hash函数建议自行设计一个,然后和成熟的hash函数比较,比如下面的ELF hash函数。
/* UNIX ELF hash
* Published hash algorithm used in the UNIX ELF format for object files
*/
unsigned long hash(char *name)
{
unsigned long h = 0, g;
while ( *name ) {
h = ( h << 4 ) + *name++;
if ( g = h & 0xF0000000 )
h ^= g >> 24;
h &= ~g;
}
return h;
}
另外,请比较线性地址法和链地址法两种冲突处理方法的性能,以及调整hash表大小对性能的影响。
注意:平衡二叉树和hash的实现必须由同学们编码完成,不能采用C++或JAVA的泛型库。
输入
输入分为两部分。
第一部分是字典,每个单词占据一行,最后以仅包含'#'的一行表示结束。所有的单词都是不同的,字典中最多10000个单词。
输入的第二部分包含了所有待检测的单词,单词数目不超过50。每个单词占据一行,最后以仅包含'#'的一行表示结束。
字典中的单词和待检测的单词均由小写字母组成,并且单词最大长度为15。
输出
按照检查次序每个单词输出一行,该行首先输出单词自身。如果单词在字典中出现,接着输出" is correct"。如果单词是错误的,那么接着输出’:’,如果字典中有建议修改单词,则按照字典中出现的先后次序输出所有的建议修改单词(每个前面都添加一个空格),如果无建议修改单词,在’:'后直接换行。
样例输入
i
is
has
have
be
my
more
contest
me
too
if
award
me
aware
m
contest
hav
oo
or
i
fi
mre
样例输出
me is correct
aware: award
m: i my me
contest is correct
hav: has have
oo: too
or:
i is correct
fi: i
mre: more me
实验设计
方法一.用线性表维护字典
题解:模糊匹配单词时,这里有三个条件能匹配成功,即对单词一个字符增,删,替能与字典库单词匹配。
这里用线性表,定义一个结构体数组,由字符串和字符串长度信息组成。
如何增删替呢?
- 替:对于上述样例,只要两个字符串长度相等,且只有一个字符不相等,我们就认为他可以替换,如m和i。
- 增:对于上述样例,相当于单词只能增加一个字母,所以字典串只能比该单词串长度大1才能满足,即单词与字典单词全都顺序匹配,只有其中一个位置需要添加一个单词,如oo与too。
- 减:与增相同,只要我们的单词串按顺序匹配,只有一个与字典单词不一样即可。
对于变量cnt,主要起到一个记录当前比较的位置,两者单词不相等的次数。具体看代码理解。
对于增和减,i,j都有特别处理,就是保持不动的处理。例如减的时候,单词串bc,字典串abc,i=0,j=0,字符不相等,i不动,j++,这样的操作!!
大佬就直接看代码吧,别听上面瞎比比,是不是说晚了? -。-
#include<stdio.h>
#include<string.h>
struct node
{
char ch[16];
int len=0;
}dic[10005],al;//dic数组为存在字典单词
int n=0,int ans[10005];
void pp(node T)
{
int i=1,j=1;
int p=0;//表示有多少与之匹配,记录其字典中序号
for(int cse=0;cse<n;cse++)
{
int cnt=0;
if(T.len==dic[cse].len){//完美匹配或模糊匹配-替
for(i=0;i<T.len;i++){
if(T.ch[i]!=dic[cse].ch[i]){
cnt++;
if(cnt>1) break;
}
}
if(cnt==0){//既完美匹配
printf("%s is correct\n",T.ch);
return;
}
}else if(T.len==dic[cse].len+1){//减
for(i=0,j=0;i<T.len;i++,j++){
if(T.ch[i]!=dic[cse].ch[j]){
cnt++;
j--;
if(cnt>1) break;
}
}
}else if(T.len==dic[cse].len-1){//增
for(j=0,i=0;j<dic[cse].len;i++,j++){
if(T.ch[i]!=dic[cse].ch[j]){
cnt++;
i--;
if(cnt>1) break;
}
}
}
if(cnt==1)
ans[p++]=cse;
}
printf("%s:",T.ch);
for(i=0;i<p;i++)
printf(" %s",dic[ans[i]].ch);
printf("\n");
return ;
}
int main()
{
while(~scanf("%s",dic[n].ch)){//输入字典单词
if(dic[n].ch[0]=='#') break;
dic[n].len=strlen(dic[n].ch);
n++;
}
while(~scanf("%s",al.ch)){//输入查找的单词
if(al.ch[0]=='#') break;
al.len=strlen(al.ch);
pp(al);
}
return 0;
}
方法二.用AVL树维护字典
- AVL:即平衡二叉树,这里主要是建树和查找树。建平衡树是关键。
- 要我用AVL做,我一开始考虑的就是就不能AC,答案顺序不一样。
- 后来想想,不就是要用AVL查字典单词怎么样快嘛,当然O(log2n)了。当然没有查到呢,我们就只能乖乖模糊匹配整个字典咯。这里做了一个花式操作,就是录入的时候,不止录入字典单词,单词长度,还加一个录入时的序号。
- 具体要做的:建平衡二叉树(先学建排序二叉树吧),再查找,查不到就是模糊匹配整颗树了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
struct node
{
char ch[16];
int len=0;
int num=0;
}dic[10005],al;
int n=0;//当前字典数
int p=0;//记录当前模糊单词数
int ans[10005];
int flag=0;//是否存在正确匹配的单词
typedef struct tree
{
node dic;
int bf;
tree *l,*r;
}BiTNode, *BiTree;
void R_Rotate(BiTree &p)
{
BiTree L;
L=p->l;p->l=L->r;L->r=p;
p=L;
}
void L_Rotate(BiTree &p)
{
BiTree R;
R=p->r;p->r=R->l;R->l=p;
p=R;
}
void LeftBalance(BiTree &T)
{
BiTree L,Lr;
L=T->l;
switch(L->bf)
{
case 1:T->bf=L->bf=0;
R_Rotate(T);
break;
case -1:Lr=L->r;
switch(Lr->bf)
{
case 1:T->bf=-1;
L->bf=0;
break;
case 0:
T->bf=L->bf=0;
break;
case -1:
T->bf=0;
L->bf=1;
break;
}
Lr->bf=0;
L_Rotate(T->l);
R_Rotate(T);
}
}
void RightBalance(BiTree &T)
{
BiTree R,Rl;
R=T->r;
switch(R->bf)
{
case -1:T->bf=R->bf=0;
L_Rotate(T);
break;
case 1:Rl=R->l;
switch(Rl->bf)
{
case -1:T->bf=1;
R->bf=0;
break;
case 0:T->bf=R->bf=0;
break;
case 1:T->bf=0;
R->bf=-1;
break;
}
Rl->bf=0;
R_Rotate(T->r);
L_Rotate(T);
}
}
int InsertBIT(BiTree &T,node e,int &tel)
{
BiTree s;
if(!T){
T=new BiTNode;
T->dic=e;
T->l=T->r=NULL;
T->bf=0;
tel=1;
}else{
if (e.len<T->dic.len)
{
if (!InsertBIT(T->l,e,tel)) return 0;
if (tel)
switch(T->bf)
{
case 1:LeftBalance(T); tel=0; break;
case 0:T->bf=1; tel=1; break;
case -1:T->bf=0; tel=0; break;
}
}
else{
if (!InsertBIT(T->r,e,tel)) return 0;
if (tel)
switch(T->bf)
{
case 1:T->bf=0; tel=0; break;
case 0:T->bf=-1; tel=1; break;
case -1:RightBalance(T); tel=0; break;
}
}
}
return 1;
}
int p=0;//记录当前模糊单词数
int ans[10005];
int flag=0;//是否存在正确匹配的单词
int pp(node S,node T)
{
int i=1,j=1;
int cnt=0;
if(T.len==S.len){//完美匹配或模糊匹配-替
for(i=0;i<T.len;i++){
if(T.ch[i]!=S.ch[i]){
cnt++;
if(cnt>1) break;
}
}
if(cnt==0){//既完美匹配
printf("%s is correct\n",T.ch);
flag=1;
return 2;
}
}else if(T.len==S.len+1){//减
for(i=0,j=0;i<T.len;i++,j++){
if(T.ch[i]!=S.ch[j]){
cnt++;
j--;
if(cnt>1) break;
}
}
}else if(T.len==S.len-1){//增
for(j=0,i=0;j<S.len;i++,j++){
if(T.ch[i]!=S.ch[j]){
cnt++;
i--;
if(cnt>1) break;
}
}
}
return cnt;
}
void FindBIT(BiTree T,node e)
{
if(flag)
return;
if (T){
if(abs(T->dic.len-e.len)<=1){
if(pp(T->dic,e)==1) ans[p++]=T->dic.num;
}
if (T->dic.len-e.len>1) FindBIT(T->l,e);
else if (T->dic.len-e.len<-1) FindBIT(T->r,e);
else{
FindBIT(T->l,e);
FindBIT(T->r,e);
}
}
}
int main()
{
BiTree T=NULL;//定义一颗树
int tel;
while(~scanf("%s",dic[n].ch))//输入字典单词
{
if(dic[n].ch[0]=='#') break;
dic[n].len=strlen(dic[n].ch);
dic[n].num=n;
InsertBIT(T,dic[n],tel);
n++;
}
while(~scanf("%s",al.ch))//输入查找的单词
{
if(al.ch[0]=='#') break;
al.len=strlen(al.ch);
flag=p=0;
FindBIT(T,al);
if(!flag){
printf("%s:",al.ch);
sort(ans,ans+p);
for(int i=0;i<p;i++)
printf(" %s",dic[ans[i]].ch);
printf("\n");
}
}
return 0;
}
方法三.采用hash技术维护字典
- 采用题目提供的hash值生成法的话,我们可以开一个hash[1005]的数组,如果生成的hash值超过1000,就把它压缩至1000以内的数,这当然会导致hash[]数组冲突了,解决冲突的办法是在hash[]内再加个数组或链表,把冲突值都写一起。这样一般查找下来,时间复杂度为O(1)。但是对于模糊单词的搜索,我们只能查找整个表咯。怪我咯,我太菜,-。-,找模糊匹配单词O(n)。
- 当然我之前还考虑了一种方便的,就是以字符串长度为单位的hash数组,这样数组可以定义为a[16]就好了,当然长度相同的就存在链表或数组里咯,这样有个好处,我们模糊匹配单词时,只要查三个表,len,len-1,len+1。最坏的情况就是所有字符都是一个长度大小,查找下来和方法一,线性表方法没区别了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct node
{
char ch[16];
int len=0;
}dic[10005],al;
struct hhash
{
int *ch=NULL;
int cnt=0;
}h[1005];
int n=0;//字典数
unsigned long ELFhash(char *name)//哈希值计算
{
unsigned long h = 0, g;
while ( *name ) {
h = ( h << 4 ) + *name++;
if ( g = h & 0xF0000000 )
h ^= g >> 24;
h &= ~g;
}
while(h>1000) h/=10;//我限制了hash值范围。
return h;
}
int pp(node T)
{
int hh=ELFhash(T.ch);
int i=1,j=1;
if(h[hh].cnt>=1){
for(int cse=0;cse<h[hh].cnt;cse++){
if(strcmp(dic[h[hh].ch[cse]].ch,al.ch)==0){
printf("%s is correct\n",al.ch);
return 1;
}
}
}
/*找不到就搜索整个表*/
printf("%s:",T.ch);
for(int cse=0;cse<n;cse++)
{
int cnt=0;
if(T.len==dic[cse].len){//完美匹配或模糊匹配-替
for(i=0;i<T.len;i++){
if(T.ch[i]!=dic[cse].ch[i]){
cnt++;
if(cnt>1) break;
}
}
}else if(T.len==dic[cse].len+1){//减
for(i=0,j=0;i<T.len;i++,j++){
if(T.ch[i]!=dic[cse].ch[j]){
cnt++;
j--;
if(cnt>1) break;
}
}
}else if(T.len==dic[cse].len-1){//增
for(j=0,i=0;j<dic[cse].len;i++,j++){
if(T.ch[i]!=dic[cse].ch[j]){
cnt++;
i--;
if(cnt>1) break;
}
}
}
if(cnt==1)
printf(" %s",dic[cse].ch);
}
printf("\n");
return 1;//success
}
int main()
{
while(~scanf("%s",dic[n].ch)){//输入字典单词
if(dic[n].ch[0]=='#') break;
dic[n].len=strlen(dic[n].ch);
int hh=ELFhash(dic[n].ch);
if(h[hh].cnt==0)//没有则建立
h[hh].ch=new int[50];
h[hh].ch[h[hh].cnt++]=n;
n++;
}
while(~scanf("%s",al.ch)){//输入查找的单词
if(al.ch[0]=='#') break;
al.len=strlen(al.ch);
pp(al);
}
return 0;
}