课程设计 - 单词检查

18 篇文章 4 订阅
12 篇文章 0 订阅

前言

各位在做课程设计的学弟学妹,请保持独立思考的能力!
复制粘贴满足自己的虚荣心,却永远解决不了你是“菜鸟”的事实。

题目描述

许多应用程序,如字处理软件,邮件客户端等,都包含了单词检查特性。单词检查是根据字典,找出输入文本中拼错的单词,我们认为凡是不出现在字典中的单词都是错误单词。不仅如此,一些检查程序还能给出类似拼错单词的修改建议单词。 例如字典由下面几个单词组成:
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;
}
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值