1、绪论
1.1 背景
随着网络技术的发展,网络安全变得越来越重要,文件加密是其中一个重要部分。而“魔王语言”就是一个简单的文件加密。“魔王语言”就是有一个魔王总是使用自己的一种非常精练而又抽象的语言讲话,没有人能听得懂,但他的语言是可以逐步解释成人能听懂的语言,因为他的语言是有规则的,可以通过相应的规则来解释。
1.2研究目的
通过这次设计,要求在数据结构逻辑特性和物理分析、数据结构的选择和应用、算法的设计及其实现等方面,加深对基础知识的理解。同时,在程序设计方法以及上机造作等基本技能方面受到严格的训练。
2、需求分析
2.1 题目
魔王语言解释。
2.2 基本要求
魔王的话没有人能听得懂,但他的语言是可以逐步解释成人能听懂的语言,有着下面的基本要求:
用下述两条具体规则和下述规则形式2)实现。设大写字母表示魔王语言的词汇;小写字母表示人的语言词汇;希腊字母表示可以用大写字母或小写字母代换的变量。魔王语言可含人的词汇。
以下是他的语言的两种形式的规则有语言逐步抽象上去的:
1)α 转换为 β1β2…βm
2)(θδ1δ2…δn) 转换为 θδnθδn-1… θδ1θ
在这两种形式重,从左到右均表示解释。试写一个魔王语言的解释兄,把他的话解释成人能听得懂的话。因此,我们编写了一个有趣的魔王语言解释程序来巩固和加强对栈和队列的理解。
以下是他的语言的两种形式的具体规则:
A 转换为 hdwhz,B 转化为 hasff,C 转化为 hwhll,D 转化为 hkhbf,E 转化为 hkwsx,F 转化为 hxwsb。
h 转化为 “花”,d 转化为 “蝴蝶”,w 转化为 “为”,z 转化为 “醉”,s 转化为 “随”,fe 转化为 “风”,f 转化为 “飞”,k 转化为 “开”,ku 转化为 “哭”,b 转化为 “悲”,x 转化为 “谢”,l 转化为 “落泪”,ba 转化为 “瓣”。
A 转化为 “蝴蝶为花瓣”,B 转化为“花却随风飞”,C 转化为“花舞花落泪”,D 转化为“花哭花瓣飞”,E 转化为“花开为谁谢”,F 转化为“花谢为谁悲”。
2.3数据测试
B(hdwz)B解释成 hasffhzhwhdhhasff
若将小写字母与汉字建立下表所示的对应关系,则魔王说的话是:“花却随风飞花醉花为花蝴蝶花花却随风飞”。
h z w d s f
花 醉 为 随 飞
2.4实现分析
以一维数组demon[ i ]表示魔王语言.
魔王语言由用户输入,初始保存在demon[ i ]中.
魔王语言与人类语言对应关系固化在程序中.
2.5实现过程
初始,魔王语言接收后存放在demon[ i ]中.
遍历数组,将数组中括号内的元素入栈,同时插入相应首字母;
再次遍历数组,将数组元素依次入队。(小写字母直接入队;大写字母翻译成相应字符入队;遇到括号,将栈中保存的元素依次出栈入队)在翻译过程中,如果依旧包含大写字母,则置flag为1,否则为0。
将队列中元素赋值给demon[ i ]。如果此时flag=1,则再次重复C过程。直至所有元素为人类语言。
输出demon[ i ]。此时数组中元素为对应的人类语言。注:如果程序中没有相应的对应关系,则翻译成“?”。
3、概要设计
3.1 数据流程图
魔王语言解释学年设计包括了主函数和栈和队列的操作两大部分,其中栈和队列的操作部分有构建、添加、删除、查找等函数。具体流程如图1:
图1 数据流程图
3.2设定栈的抽象数据类型定义:
ADT stack{
数据对象:D={ai |ai∈CharSet,i=1,2,…,n,n>=0}
数据关系:R1={ 基本操作: Initstack(&s) 操作结果:构造一个空栈s. Push(&s,e) 初始条件:栈s已存在. 操作结果:在栈s的栈顶插入新的栈顶元素e. Pop(&s,&e) 初始条件:栈s已存在. 操作结果:删除s的栈顶元素,并以e返回其值. }ADT stack 3.3设定队列的抽象数据类型: ADT queue{ 数据对象:D={ai |ai∈Elemset,i=1,2,…,n,n>=0} 数据关系:R1={ 基本操作: Initqueue(&q) 操作结果:构造一个空队列q. Enqueue(&q,e) 初始条件:队列q已存在. 操作结果:插入元素e为q的新队尾元素. Dequeue(&q,&e) 初始条件:q为非空队列. 操作结果:删除q的队头元素,并用e返回其值. }ADT queue 3.4本程序包含四个模块: 主函数模块,其中主函数为: Status main() { 初始化栈; 初始化队列; 接收魔王语言输入到数组demon[i]; 遍历数组将括号中元素进栈; While(数组demon[i]中元素有大写字母) { 翻译排序处理后入队列; 将队列元素保存在数组demon[i]; } 输出人类语言(数组demon[i]); } 括号内元素入栈处理模块. Tempstack(&temps) 将括号内元素入栈,依次插入首字符. 举例:(abcd)->adacaba. 排序入队列模块. Sort(&s,&q) { 遍历数组; { 遇到小写字母,直接入队列; 遇到大写字母,翻译大写后入队列; 遇到括号,将栈中保存的元素依次出栈入队; } } 翻译大写处理模块. Spenqueue(&*q,key) { Switch(key) { 找到各个大写字母对应的字符串. 没有相应的则解释为‘?’ } } 各模块之间调用关系: 主函数模块 { 括号内元素入栈处理模块: 排序入队模块 { 翻译大写处理模块; } } 4、详细设计 4.1标准库函数及宏的定义 #include #include #define STACK_INIT_SIZE 100 #define STACK_INCREMENT 10 4.2 定义主函数 主函数流程图如图2所示 图2 主函数流程图 主函数的编写,在主函数中编译魔王语言所遵循的规则,及栈、队列、相关数组结构的定义与调用,及输出结果的显示 int main() //主函数从此开始进行 { printf("**************************************************************\\n"); printf("* * 欢迎来到滁州学院 * *\\n"); printf("* **************************** *\\n"); printf("* * 魔王语言解释系统 * *\\n"); printf("* **************************** *\\n"); printf("* * 班级:网络工程(2)班 * *\\n"); printf("* * 组别:组1 * *\\n"); printf("**************************************************************\\n"); int xunhuan=1;//是下面的 while循环一定执行 printf("请输入你想要解释的魔王语言:\\n"); while (xunhuan==1) //一个总循环控制整个程序的重复进行 { char A[7]="hdwhz,"; //大写字母作为字符数组名存放小写字母 char B[7]="hqsff,"; char C[7]="hwhll,"; char D[7]="hkhbf,"; char E[7]="hkwsx,"; char F[7]="hxwsb!"; char flag='0'; //flag用来标记处理括号 char e1,key,e2,e; //定义char型变量 int mark=1; //标记输入的魔王语言是否在允许的范围之内 int f=1; // 判断括号是否匹配 char MoWang[100]="\\0"; //定义一个魔王变量,存放待解释的语言字符 struct Stack S; //作为栈存储元素,为后续操作和输出做准备 struct Stack temp; //用来处理括号外的元素 InitStack(S); InitStack(temp); struct LinkQueue Q; InitQueue(Q); gets(MoWang); //变量MoWang存储输入的语言,讲魔王的话放入数组中 InStack(MoWang,S); //把要解释的魔王语言压入栈中 while(!StackEmpty(S)) //把魔王语言进行出栈,不符合语言的进行提示 { Pop(S,e1); if(e1=='(') { if(StackEmpty(S)) { printf("魔王语言错误!\\n"); mark=0;f=0; break; } while(!StackEmpty(S)) { Pop(S,e1); if(e1==')') { f=1; break; } else if(!(e1>='a'&&e1<='z')&&!(e1>='A'&&e1<='Z')) { printf("魔王语言错误!\\n"); mark=0; break; } } if(mark==0) break; if(f!=1) { printf("魔王语言错误!\\n"); break; } } else if(e1==')') { printf("魔王语言错误!\\n"); mark=0; break; } else if(!(e1>='a'&&e1<='z')&&!(e1>='A'&&e1<='Z')) { printf("魔王语言错误!\\n"); mark=0; break; } } if(mark==1&&f==1) //对符合语言规则的魔王语言进行规则处理 { ClearStack(S); InStack(MoWang,S); //把魔王语言从右至左压栈存放 while(!StackEmpty(S)) //栈不空时,用栈temp进行存储不带括号内元素的元素 { Pop(S,e1); if(e1=='B'||e1=='A'||e1=='C'||e1=='D'||e1=='E'||e1=='F') Push(temp,e1); else if(e1=='(') //用队存储括号中的元素 { Push(temp,flag); //有括号的话就用flag标记 Pop(S,e1); while(e1!=')') //把括号中的元素存入队列中 { EnQueue(Q,e1); Pop(S,e1); } if(!QueueEmpty(Q)) DeQueue(Q,key); //将队头的元素赋值给key } else Push(temp,e1); } while(!StackEmpty(temp)) //将魔王说的语言规则地压入栈s中 { Pop(temp,e1); if(e1!=flag) Push(S,e1); //把括号外的元素压入中 else{ while(!QueueEmpty(Q)) //处理括号中的元素进栈 { DeQueue(Q,e2); Push(S,key); Push(S,e2); } Push(S,key); //最后还要压一个key } } printf("解释后的语言为:\\n"); while(!StackEmpty(S)) //依次出栈输出处理后的元素 { Pop(S,e); EnQueue(Q,e); //元素进队是为了输出对应汉字 if(e=='B') printf("%s",B); else if(e=='A') printf("%s",A); else if(e=='C') printf("%s",C); else if(e=='D') printf("%s",D); else if(e=='E') printf("%s",E); else if(e=='F') printf("%s",F); else printf("%c",e); } printf("\\n"); while(!QueueEmpty(Q)) //翻译成对应的汉字 { DeQueue(Q,e); switch(e) { case 'h': printf("花");break; case 'd' : printf("蝴蝶"); break; case 'w' : printf("为"); break; case 'z' : printf("醉"); break; case 's' : printf("随"); break; case 'fe' : printf("风"); break; case 'f' : printf("飞"); break; case 'k' : printf("开"); break; case 'ku' : printf("哭"); break; case 'b' : printf("悲"); break; case 'x' : printf("谢"); break; case 'l' : printf("落泪"); break; case 'ba' : printf("瓣"); break; case 'B' : printf("花却随风飞,");break; case 'A' : printf("蝴蝶为花醉,");break; case 'C' : printf("花舞花落泪,");break; case 'D' : printf("花哭花瓣飞,");break; case 'E' : printf("花开为谁谢,");break; case 'F' : printf("花谢为谁悲!");break; default : printf("*");break; } } printf("\\n"); } printf("再次输入魔王语言,按数字键:1---继续;0----退出:\\n"); scanf("%d",&xunhuan); } return 0; } 4.3栈的初始化、进栈、出栈、栈的判空、栈的清空 定义栈:栈又称堆栈,它是一种运算受限的线性表。其是仅允许在表的一端插入和删除,这一端叫做栈顶。另一端叫做栈底。而由于栈是一种运算受限的线性表,所以他的空间也是有限的,即栈的空间大小。 struct Stack //定义栈 { char* base; char* top; int stacksize; }; 构造栈:构造一个空栈,定义空栈的空间大小。初始时栈顶指针top,指向实际栈顶空位置,初值为0. void InitStack(struct Stack &s) //构造栈 { s.base=(char*)malloc(STACK_INIT_SIZE*sizeof(char));//栈动态分配空间 s.top=s.base;//栈中无元素 s.stacksize=STACK_INIT_SIZE;//栈的最大空间为分配空间 } 往栈中压入元素:用s.base来表示栈底;用s.top来表示栈顶。在往栈中压入元素的时候,首先判断栈的空间大小是否满足承载所有元素。如果元素溢出,则重新定义一个栈的空间大小,使元素能完全进入。 void Push(struct Stack &s,char e)//往栈中压入元素 { if(s.top-s.base>=STACK_INIT_SIZE)//超出分配空间 { s.base=(char*)realloc(s.base,(s.stacksize+STACK_INCREMENT)*sizeof(char));//再分配 s.top=s.base+s.stacksize;//栈顶指针上移 s.stacksize+=STACK_INCREMENT;//栈的空间增加 } //不超过分配空间 *(s.top)=e; s.top++; } 取出栈中的元素:出栈时,遵循先进后出的原则。提取s.top指向的前一个元素。 void Pop(struct Stack &s,char &e) //取出栈中的元素 { e=*--s.top; } 判断栈是否为空:当提取一个元素之后,进行判断。判断s.top和s.base是否满足s.top==s.base。如果满足则栈为空,如果不满足则栈不为空程序继续进行。 int StackEmpty(struct Stack s) //判断栈是否为空 { if(s.top==s.base) return 1; else return 0; } void ClearStack(struct Stack &s) //清空栈 { s.top=s.base; } 把字符数组从右至左压入栈中:首先统计所有字符数据,然后将统计的字符按顺序从右往左压入栈中。 void InStack(char* ch,struct Stack &s)//把字符数组从右至左压入栈中 { int i,L=0; while(ch[L]!='\\0') L++; for(i=L-1;i>=0;i--) Push(s,ch[i]); } 4.4队列的初始化、进队列、出队列、判空 定义队列:队列时只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表。允许插入的一端为队尾,允许删除的一端为队头。 struct Queue //定义队列 { char data; struct Queue* next; }; struct LinkQueue { struct Queue* front; struct Queue* rear; }; 构造队列:构造一个队列,定义队列的空间大小,并确定队头。 void InitQueue(struct LinkQueue &q) //构造队列 { q.front=q.rear=(struct Queue*)malloc(sizeof(struct Queue)); q.front->next=NULL; } 元素入队:元素入队遵循先进先出原则。定义一个指针P,并确定指针的空间大小。同时使指针P所在的地址为队头,即确定队头。 void EnQueue(struct LinkQueue &q,char e) //元素入队 { struct Queue* p; p=(struct Queue*)malloc(sizeof(struct Queue)); p->data=e; p->next=NULL; q.rear->next=p; q.rear=p; } 元素出队:定义一个指针型队列P,然后让队列里的元素出队。之后判断队列是否为空,如果不为空,则释放队列。如果为空,则队头就是队尾,即只有一个元素,然后再释放队列。 判断队列是否为空,如果对为空,返回,否则返回 void DeQueue(struct LinkQueue &q,char &e) //元素出队 { struct Queue* p; p=q.front->next; e=p->data; q.front->next=p->next; if(q.rear==p) q.rear=q.front; free(p); } 判空 int QueueEmpty(struct LinkQueue q) //判断队列是否为空,如果对为空,返回,否则返回 { if(q.front==q.rear) return 1; else return 0; } 5、调试结果 输入正确的魔王语言ABCDEF经翻译为“蝴蝶为花醉,花却随风飞,花舞花落泪,花哭花瓣飞,花开为谁谢,花谢为谁悲”。若继续翻译魔王的语言按数字1,退出程序按数字0 调试结果如图3所示:输入语言在规则范围内的正确调试结果 图3 符合测试要求的运行结果 输入魔王语言ZJIAEFD,因为又不在规则范围内的语言所以翻译为“***蝴蝶为花醉,花开为谁谢,花谢为谁悲!花哭花瓣飞,”。若继续翻译魔王的语言按数字1,退出程序按数字0 调试结果如图4所示:输入语言部分在规则范围内的调试结果 图4 部分符合测试要求的运行结果 输入错误的魔王语言UKUITYOJOIOI;LOW则显示魔王语言错误。若继续翻译魔王的语言按数字1,退出程序按数字0 调试结果如图5所示 :输入语言全部不在规则范围内的调试结果及错误的魔王语言 图5 完全不符合测试要求的运行结果 6、学年设计总体设计与体会 在设计的过程中,主要使用到了数据结构中的栈和队列,将魔王的语言自右至左进栈,总是处理栈顶字符。若是开括号,则逐一出栈,将字母顺序入队列,直至闭括号出栈,并按规则要求逐一出队列在处理后入栈。此课程设计旨在解决魔王的语言逐步翻译为人能听懂的语言,可扩充为对相应简单加密的文件进行翻译解释。通过三次分别测试,包括符合测试要求、部分符合测试要求、不符合测试要求,并且实验结果和我们预想的一样,说明了魔王语言解释系统是符合学年设计任务要求的。 本学年设计,一方面在透彻地理解c语言和数据结构的基本概念和原理之上,使之由抽象理论转变成了具体的实际应用,化抽象为具体;另一方面,通过本学年设计极大地增强了该小组成员的实验手段与实践技能,提高了发现问题、分析问题、解决问题的能力。培养了应用知识的能力、团队合作精神和创新精神。虽然遇到了各种各样的问题,但是在设计的过程中我们发现了自己的不足之处,对以前所学过的知识理解得不够深刻,掌握得不够牢固,不过通过对所学课程的复习和老师的耐心指导设计终于顺利完成了,在此再次感谢刘老师。 7、参考文献 [1] 严蔚敏,吴伟民.数据结构[M].北京:清华大学出版社,2002. [2] 严蔚敏,吴伟民.数据结构题集(C语言版)[M].北京:清华大学出版社,2003. [3] 胡学钢.数据结构(C语言版) [M].北京:高等教育出版社,1999. 8、附录 #include #include #define STACK_INIT_SIZE 100 #define STACK_INCREMENT 10 struct Stack //定义栈 { char* base; char* top; int stacksize; }; void InitStack(struct Stack &s) //构造栈 { s.base=(char*)malloc(STACK_INIT_SIZE*sizeof(char));//栈动态分配空间 s.top=s.base;//栈中无元素 s.stacksize=STACK_INIT_SIZE;//栈的最大空间为分配空间 } void Push(struct Stack &s,char e)//往栈中压入元素 { if(s.top-s.base>=STACK_INIT_SIZE)//超出分配空间 { s.base=(char*)realloc(s.base,(s.stacksize+STACK_INCREMENT)*sizeof(char));//再分配 s.top=s.base+s.stacksize;//栈顶指针上移 s.stacksize+=STACK_INCREMENT;//栈的空间增加 } //不超过分配空间 *(s.top)=e; s.top++; } void Pop(struct Stack &s,char &e) //取出栈中的元素 { e=*--s.top; } int StackEmpty(struct Stack s) //判断栈是否为空 { if(s.top==s.base) return 1; else return 0; } void ClearStack(struct Stack &s) //清空栈 { s.top=s.base; } struct Queue //定义队列 { char data; struct Queue* next; }; struct LinkQueue { struct Queue* front; struct Queue* rear; }; void EnQueue(struct LinkQueue &q,char e) //元素入队 { struct Queue* p; p=(struct Queue*)malloc(sizeof(struct Queue)); p->data=e; p->next=NULL; q.rear->next=p; q.rear=p; } void DeQueue(struct LinkQueue &q,char &e) //元素出队 { struct Queue* p; p=q.front->next; e=p->data; q.front->next=p->next; if(q.rear==p) q.rear=q.front; free(p); } int QueueEmpty(struct LinkQueue q) //判断队列是否为空,如果对为空,返回,否则返回 { if(q.front==q.rear) return 1; else return 0; } void InStack(char* ch,struct Stack &s)//把字符数组从右至左压入栈中 { int i,L=0; while(ch[L]!='\\0') L++; for(i=L-1;i>=0;i--) Push(s,ch[i]); } int main() //主函数从此开始进行 { printf("**************************************************************\\n"); printf("* * 欢迎来到滁州学院 * *\\n"); printf("* **************************** *\\n"); printf("* * 魔王语言解释系统 * *\\n"); printf("* **************************** *\\n"); printf("* * 班级:网络工程(2)班 * *\\n"); printf("* * 组别:组1 * *\\n"); printf("**************************************************************\\n"); int xunhuan=1;//是下面的 while循环一定执行 printf("请输入你想要解释的魔王语言:\\n"); while (xunhuan==1) //一个总循环控制整个程序的重复进行 { char A[7]="hdwhz,"; //大写字母作为字符数组名存放小写字母 char B[7]="hqsff,"; char C[7]="hwhll,"; char D[7]="hkhbf,"; char E[7]="hkwsx,"; char F[7]="hxwsb!"; char flag='0'; //flag用来标记处理括号 char e1,key,e2,e; //定义char型变量 int mark=1; //标记输入的魔王语言是否在允许的范围之内 int f=1; // 判断括号是否匹配 char MoWang[100]="\\0"; //定义一个魔王变量,存放待解释的语言字符 struct Stack S; //作为栈存储元素,为后续操作和输出做准备 struct Stack temp; //用来处理括号外的元素 InitStack(S); InitStack(temp); struct LinkQueue Q; InitQueue(Q); gets(MoWang); //变量MoWang存储输入的语言,讲魔王的话放入数组中 InStack(MoWang,S); //把要解释的魔王语言压入栈中 while(!StackEmpty(S)) //把魔王语言进行出栈,不符合语言的进行提示 { Pop(S,e1); if(e1=='(') { if(StackEmpty(S)) { printf("魔王语言错误!\\n"); mark=0;f=0; break; } while(!StackEmpty(S)) { Pop(S,e1); if(e1==')') { f=1; break; } else if(!(e1>='a'&&e1<='z')&&!(e1>='A'&&e1<='Z')) { printf("魔王语言错误!\\n"); mark=0; break; } } if(mark==0) break; if(f!=1) { printf("魔王语言错误!\\n"); break; } } else if(e1==')') { printf("魔王语言错误!\\n"); mark=0; break; } else if(!(e1>='a'&&e1<='z')&&!(e1>='A'&&e1<='Z')) { printf("魔王语言错误!\\n"); mark=0; break; } } if(mark==1&&f==1) //对符合语言规则的魔王语言进行规则处理 { ClearStack(S); InStack(MoWang,S); //把魔王语言从右至左压栈存放 while(!StackEmpty(S)) //栈不空时,用栈temp进行存储不带括号内元素的元素 { Pop(S,e1); if(e1=='B'||e1=='A'||e1=='C'||e1=='D'||e1=='E'||e1=='F') Push(temp,e1); else if(e1=='(') //用队存储括号中的元素 { Push(temp,flag); //有括号的话就用flag标记 Pop(S,e1); while(e1!=')') //把括号中的元素存入队列中 { EnQueue(Q,e1); Pop(S,e1); } if(!QueueEmpty(Q)) DeQueue(Q,key); //将队头的元素赋值给key } else Push(temp,e1); } while(!StackEmpty(temp)) //将魔王说的语言规则地压入栈s中 { Pop(temp,e1); if(e1!=flag) Push(S,e1); //把括号外的元素压入中 else{ while(!QueueEmpty(Q)) //处理括号中的元素进栈 { DeQueue(Q,e2); Push(S,key); Push(S,e2); } Push(S,key); //最后还要压一个key } } printf("解释后的语言为:\\n"); while(!StackEmpty(S)) //依次出栈输出处理后的元素 { Pop(S,e); EnQueue(Q,e); //元素进队是为了输出对应汉字 if(e=='B') printf("%s",B); else if(e=='A') printf("%s",A); else if(e=='C') printf("%s",C); else if(e=='D') printf("%s",D); else if(e=='E') printf("%s",E); else if(e=='F') printf("%s",F); else printf("%c",e); } printf("\\n"); while(!QueueEmpty(Q)) //翻译成对应的汉字 { DeQueue(Q,e); switch(e) { case 'h': printf("花");break; case 'd' : printf("蝴蝶"); break; case 'w' : printf("为"); break; case 'z' : printf("醉"); break; case 's' : printf("随"); break; case 'fe' : printf("风"); break; case 'f' : printf("飞"); break; case 'k' : printf("开"); break; case 'ku' : printf("哭"); break; case 'b' : printf("悲"); break; case 'x' : printf("谢"); break; case 'l' : printf("落泪"); break; case 'ba' : printf("瓣"); break; case 'B' : printf("花却随风飞,");break; case 'A' : printf("蝴蝶为花醉,");break; case 'C' : printf("花舞花落泪,");break; case 'D' : printf("花哭花瓣飞,");break; case 'E' : printf("花开为谁谢,");break; case 'F' : printf("花谢为谁悲!");break; default : printf("*");break; } } printf("\\n"); } printf("再次输入魔王语言,按数字键:1---继续;0----退出:\\n"); scanf("%d",&xunhuan); } return 0; }