电话号码对应单词1
一、题目基本描述
电话的号码盘一般可以用于输入字母。如用2可以输入ABC,用3可以输入DEF等。我们平时用电话发短信时,正是利用了这样的特性。例如,在拼音输入法条件下,如果要输入“你”这个字,要依次按下“6”与“4”(分别代表N与I)。当我们按下“6”与“4”后,一般的手机除了NI的拼音组合外,还会显示MI的拼音组合:这是由于6既可以代表N,又可以代表M。而MI又是合理的拼音结构。同样得到显示的还有NG(读“嗯”):虽然这并不是常用的拼音。值得一提的是,一种好的手机输入法会将这三种拼音方法按NI、MI、NG的顺序排列。这种排列并不是没有道理的:在我们发短信时,用到的NI组合最多,MI次之,而NG最少。进一步,对于手机来说,“联想功能”对我们输入短信也很有帮助:例如,在输入了“96”后,一个智能手机不只显示出了“WO”——“我”的拼音,同时显示的还有“YO”与“ZO”。虽然后两者不符合一般的拼音规则,但确是一些符合规则的拼音的前半部分:例如,“ZO”就是“ZOU”与“ZONG”的前半部分。同时,这种输入还要求有一定的容错功能,如连续输入3个“3”时,系统会报告错误(虽然这可能不会反映在手机的界面上),这是由于没有任何一个拼音组合可以用3个“3”代表。现在,希望开发一个具有类似功能的软件,能够实现上述的类似功能。与上面功能不同的是,在这里需要大家开发一般性的输入系统——不只是基于汉语拼音的输入。这个系统的框架如下:图1:系统框架词典、查询文件以及输出结果文件的格式将在下面详细说明。
二、解题思路本次大作业主要考察同学使用线性表、树或者Hash表等数据结构及查找相关算法解决问题的能力,同时锻炼同学按照函数接口编写完整程序的能力。
三、接口定义及输出格式要求 1、程序接口参数定义整个程序的主函数用 main(argc,argv)实现参数传递。学生程序根据该字符串读取码本文件路径、查询文件路径和输出结果文件路径等参数。格式如下: C://dict.txt? C://query.txt C://res.txt 也就是要求学生程序可以在命令行中运行,并且可以接收上面的参数,如下图所示:其中可执行文件后的第一个文件是码本文件及路径,第二个文件是记录要查询的电话号码文件及路径,第三个文件则是记录程序执行结果的输出文件及路径。注意:为了统一批改,本次大作业的工程名一律为 SearchWord。为了防止各种诡异错误,建议输入输出文件使用完整的绝对路径。 2、码本文件格式每次测试都将给出一个词典(eg, codebook.txt),每个词典包含以下信息: A 25 ABILITY 1 ABOUT 1 ADDED 1 AFFLUENT 1 …… 词典中的条目按每行一条的顺序排列。词典共由两列组成,第一列为词条,第二列为对应的每个词条的频度得分,得分越高的词条出现的概率越大。词典中所有的字母都是用大写表示的。每个词条的字符数最多不会超过25个(含25个)。一行的字符数最多不会超过30个(含30个)——注意,一个制表符/t是一个字符。注意:词条在文件中的先后顺序没有任何意义,词条与得分之间用“空格”或(与)“/t”组成的串分隔;词条之间用回车分隔;词典的最后一行不为空行。 3、查询文件格式每次测试将同时给出一个查询文件,每个独立的查询文件中包含了多个输入的请求数字串: 266 2245489 1 因为我们的手机键盘上与字母相对应的数字只有2~9这八个数字,但是0,1,#,*键也是可以输入的,即可以包含在查询文本中,同学需要对这样的情况进行考虑;对于查询文件为空的情况也应进行考虑。注意:每个请求数字串之间用回车分隔;查询文件的最后一行不为空行。 4、输出文件格式每次测试将产生一个查询结果的输出文件,针对上面的查询文件和我们给同学的码本,所得的查询结果输出文件如下所示: 266 2 COME BOND 1 AMONG 1 ANNOUNCED 3 CONGESTION CONFERENCE CONSCIENCE 1 CONSTITUENTS 1 CONSIDERATION 2245489 1 ABILITY 1 0 在查询结果文件中,不同的查询数字串的查询结果之间用1到2个回车符分隔。同一个查询数字串的查询结果之间不能有多余的空行。对于每一个查询数字串,第一行输出该数字串,从第二行开始输出查询到的字符串结果:第二行为仅使用查询文件中的数字作为输入,得到的搜索结果(如果存在);下一行为使用查询文件中的数字作为输入,并以任意1个附加字符作为后缀时得到的结果(如果存在);下一行为使用查询文件中的数字作为输入,并以任意2个附加字符作为后缀时得到的结果(如果存在)……依此类推。对于每一行搜索结果,首先输出查询到的匹配码本的个数,然后输出匹配的码本,个数与码本之间,码本与码本之间用空格符分隔。匹配的多个码本需要进行排序后输出,首先按照得分高低进行排序,对于得分相同的码本则按照字符串的大小顺序由小到大排列2。当没有与输入的数字串相匹配的码本时输出0。如上面的例子中数字串1的查询结果为0。当查询文件为空的情况,查询结果文件也应该为空。 5、示例测试数据随本文档还包括了一个码本文件的示例(codebook.txt)、一个输入文件的示例(query.txt)与一个输出文件的示例(res.txt),其内容为前面所举的格式示例。我们同时还提供给同学一个用户界面的FormatCheck.exe格式检查程序,界面如下图所示:点击“文件”菜单下的“打开”子菜单,选择你的查询结果输出文件。此时显示“提示”对话框。
解法思路概要:
这个算法思路很普遍.
对于词典存储结构的建立
由于只有8个数字,所以可以考虑建立八棵八叉树,每个八叉树的结点代表一个数字,如下图:
当然,八叉树的规模比这个庞大。这里只是做个示例。在把词典存入树的过程采用的是动态分配的方法,例如词典apple,则转换成对应的键盘数字应该是27753,那么就按照这个顺序,先找到树GT[2],在依次看数字7对应的结点,如果GT[2]的该子树为空,则动态分配一个,然后以此为根结点继续往下找下一个数字对应的子树,依次类推,当找到数字的最后一位时,把该词条放到这个结点里面。动态分配可以保证空间的不浪费,在词典很大的情况下,如果采用静态分配空间,那是很大的。
关于词典的查找:
词典查找是这样的。对于读入的一个查找数字串,先以此找到直接查询的结果,如果结果不存在,则调用分层遍历函数来进行遍历。这里需要说明一下的就是,对于一个特定的查询数字串,其直接查询到的词条的字母数必然等于查询数字数,而且任意附加一个字符就是对应的以直接查询到的结点的下一层子树,任意附加两个字符就是对应子树的子树,依次类推。
所以在这里是这样考虑的,可以预先建立25个动态链表HL[25],每个链表存储对应分层遍历的相同字母数的单词,在分层遍历过程中,每遍历一个单词,就对其进行判断插入,插入过程考虑得分最高的排在最前面,得分相等而字符小的排在前边的规则。
这样在查询完之后,就可以依次将链表里的数据写入到文件里了,省却了一些麻烦。
这个算法的缺点就是指针太多,容易发生错误,在修改的时候也很容易出错。就因为指针的问题导致在实现查找过程的时候走了不少弯路,而且由于时间的限制,对以一些特殊情况尽管完全可以考虑,但还是没有做,很遗憾。
这个算法的效率还是比较高的。
这个实际问题的难点就在于词典存储结构的建立和怎么将查找结果进行保存的问题。
代码(有些BUG没有及时纠正,在参考的时候要注意一些特殊情况,复制不能复制格式,将就看吧)
#include
#include
#include
#include
#include
using namespace std;
//线性链表
struct ElemType { char word[25]; int score; };
struct linkNode { ElemType data; linkNode *next; };
linkNode* InsertList(linkNode *HL,ElemType *item) { int i=0; //cout<<" item score:"<
if(HL==NULL)//链表头为空,则直接插入 {//cout<<"!!!!!"; HL=newp; //cout<<"#####";cout<
linkNode *q=HL; linkNode *p=q->next; //cout<<" again";//匹配的码本之间按要求进行插入 //得分高低 if(q->data.score
while(p!=NULL&&q->data.score>newp->data.score&&p->data.score>newp->data.score)//如果待插入的词条得分比当前的链表头两个得分都低,则需要往向后移查找合适的插入位置 { q=p; p=q->next; }//满足后边条件的情况可以退出,满足p为空的情况也可以退出,所以后边要分情况进行讨论 if(p!=NULL&&q->data.score>newp->data.score&&p->data.score
else if(p==NULL&&q->data.score>newp->data.score)//之后的那个位置为空 { newp->next=p; q->next=newp; } return HL; }
struct WordNode { char word[25]; int freq; WordNode *next; };
struct ETreeNode { WordNode *data; int total;
ETreeNode *link[8]; }; //队列结构 struct sNode{ ETreeNode data; sNode *next; }; struct QueueLK{ sNode *front; sNode *rear; };
void InitQueue(QueueLK &HQ) { HQ.front=HQ.rear=NULL; }
void EnQueue(QueueLK &HQ,ETreeNode *item) {
sNode *newptr=new sNode; if(newptr==NULL) {cerr<<"allocation failure!"< ETreeNode temp=HQ.front->data; sNode *p=HQ.front; HQ.front=p->next; if(HQ.front==NULL)HQ.rear=NULL; delete p; return temp; } bool EmptyQueue(QueueLK &HQ) { return HQ.front==NULL; } void Init(ETreeNode >) { GT.data=NULL; for(int j=0;j<8;j++) GT.link[j]=NULL; } //树的按层遍历算法 //按层遍历 void LayerOrder(ETreeNode *GT,FILE *tp) { linkNode *HL[25];//线性表用来存储按层遍历的同一层上的所有已经排好序的单词(这些单词的字母个数都应该是相同的),因为每个单词至多只有25个字母,所以需要设置25个这样的结构 int count=0; ElemType *insert=new ElemType; QueueLK q; InitQueue(q); ETreeNode *p=new ETreeNode; int tt=0; int j1; int i; for(i=0;i<25;i++) HL[i]=NULL;//cout<<"Haha"< if(p->data!=NULL) { count=0; j1=0; while(p->data->word[j1]!='0') { j1++; count++;//记录有多少个非零字母 } for(i=0;i<25;i++) { insert->word[i]=p->data->word[i]; } insert->score=p->data->freq; HL[count]=InsertList(HL[count],insert); if(HL[count]) { int u=0; } p->data=p->data->next; } if(GT!=NULL)EnQueue(q,GT); while(!EmptyQueue(q)) { *p=OutQueue(q); // cout<<" total"< for(i=0;i<25;i++) { linkNode *tem=HL[i]; if(tem!=NULL)fprintf(tp,"%c",'/n'); while(tem!=NULL) { tt=0; while(tem->data.word[tt]!='0') { tt++;//cout< } //最后链表HL的输出算法 } void query(ETreeNode *GT[8],int num[],int length,FILE *tp)//次数的query只是仅使用查询文件中的数字作为输入得到的结果 { int i; int j=0,j1=0; int sq=0; int count9=0; ETreeNode *q; q=GT[num[0]]; for(i=1;i main(int argc,char *argv[]) { FILE *fp; FILE *tp; FILE *qp; char wordlist[50]; char templist[50]; char temp; int fre; int i,j; int tag,tag2; int tag1=0; int t=0; ETreeNode *GT[8]; ETreeNode *p; //Init(top); for(i=0;i<8;i++)//初始化GT { ETreeNode *node=new ETreeNode; for(j=0;j<8;j++) { node->link[j]=NULL;//cout<<"hello"< if((fp=fopen(argv[1],"r+"))==NULL) { cout<<"cannot open this file!"< for(i=0;i<25;i++) { fscanf(fp,"%c",&wordlist[i]); } for(i=0;i<25;i++) if(wordlist[i]==' '){wordlist[i]='0';} while(!feof(fp))//每个while循环实现每个词条的存储 { fscanf(fp,"%d",&fre); fscanf(fp,"%c",&temp); if(wordlist[0]>=65&&wordlist[0]<=67)tag=0; else if(wordlist[0]>=68&&wordlist[0]<=70)tag=1; else if(wordlist[0]>=71&&wordlist[0]<=73)tag=2; else if(wordlist[0]>=74&&wordlist[0]<=76)tag=3; else if(wordlist[0]>=77&&wordlist[0]<=79)tag=4; else if(wordlist[0]>=80&&wordlist[0]<=83)tag=5; else if(wordlist[0]>=84&&wordlist[0]<=86)tag=6; else if(wordlist[0]>=87&&wordlist[0]<=90)tag=7;//cout<<"tag->"< if(p->link[tag2]==NULL)//如果需要加入词条的树节点为空,则就要增加一个结点作为存储 { ETreeNode *s=new ETreeNode; s->data=NULL; s->total=0; for(i=0;i<8;i++) {s->link[i]=NULL;} p->link[tag2]=s; p=p->link[tag2];//p指向下一个方向的生成结点 } else p=p->link[tag2];//否则直接指向下一个生成树的结点 t++;//结点号加1,即指向下一个字母 }//while如果第一个只有A则while循环没有被执行 //while(p->data){cout<<"eeeee"< WordNode *temp2=new WordNode; temp2->freq=fre; temp2->next=NULL; for(i=0;i<25;i++) { temp2->word[i]=wordlist[i]; } tr->next=temp2; } for(i=0;i<25;i++) { fscanf(fp,"%c",&wordlist[i]); } for(i=0;i<25;i++) if(wordlist[i]==' '){wordlist[i]='0';} } fclose(fp); if((tp=fopen(argv[3],"w+"))==NULL) { cout<<"cannot open this file!"< //编写查询算法 i=0; int length2=0; char num[1000]={'!'}; int numint[1000]={-1}; fscanf(qp,"%c",&num[i]); while(!feof(qp)) { if(num[i]=='/n') { //cout<<"length2"<<"?"< query(GT,numint,length2,tp);//每次调用的时候要进行写入 length2=-1;i=-1; fprintf(tp,"%c%c",'/n','/n'); } //cout<<"num[i]"< cout<<"OK,虽然不太完美,但还是要去做别的作业了"<