1. 实验目的
1.1线性表的实验目的
- 会用链表实现线性表;
- 了解线性表的基本结构和实现方法;
- 会应用线性表解决一些实际的问题;
1.2栈和队列的实验目的
- 加深理解栈的概念,掌握栈的实现方法和使用。
- 掌握使用栈来实现中缀表达式求值的方法和具体逻辑。
- 会使用栈和队列解决一些实际问题。
1.3二叉树的实验目的
(1) 掌握二叉树的实现。
(2) 掌握二叉树的应用。
1.4图结构的实验目的
(1) 掌握图结构的实现。
(2) 掌握图结构的应用。
2. 实验内容与实验步骤
2.1线性表的应用
2.1.1实验内容
用线性表存储学生的各科成绩,并可以统计某门课的最高分、最低分、平均分情况,统计成绩的功能,同时我在实验要求的基础上加上了打印总成绩单的功能。
2.1.2实验步骤
- 选定学生成绩统计的题目,进行前期的分析,使用链表存储学生的成绩,结构体节点有姓名、语文成绩、数学成绩、英语成绩、指向下一个节点的指针5部分组成。、
- 基本操作包括,创建节点、插入节点、遍历打印链表以实现成绩打印、求最大值最小值,求平均分等等。
- 根据前期设计编写代码。
- 设计测试用例,运行程序,测试程序可行性。
2.2栈和队列的应用
2.2.1实验内容
用户输入一个中缀表达式,程序将表达式通过两个栈(操作数栈和操作符栈)进行运算,最终计算出中缀表达式的结果。
2.2.2实验步骤
- 首先进行实验的需求分析,实验要计算中缀表达式的值,需要两个栈来存储操作数和操作符,c语言中没有栈的有文件,需要用链表来实现栈,同时定义出栈、入栈、返回栈顶值、判断栈是否为空的函数。
- 设计程序的大致结构,理清中缀表达式求值的逻辑。
1.初始化两个栈:运算符栈s1,存储中间结果的栈s2;
2.从左至右扫描中缀表达式;
3.遇到操作数时,将其压入s2;
4.遇到运算符时,比较其与s1栈顶运算符的优先级:
【1】如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
【2】否则,若优先级比栈顶运算符的优先级高,则将运算符压入s1;
【3】否则,将s1栈顶的运算符弹出并压入到s2中,再次转到步骤4-1与s1中新的栈顶运算符相比较;
5.遇到括号时:
【1】如果是左括号“(”,则直接压入s1;
【2】如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,【3】此时将这一对括号丢弃;
【4】重复步骤,直到表达式的最右边;
【5】将s1中剩余的运算符依次弹出并压入s2;
依次弹出s2中的元素计算结果。
2.3 二叉树的应用
2.3.1实验内容
- 使用树结构存储文件“definitions.txt”中的医院结构,格式如“hospital 10 floor”,表示hospital有10层floor。
- 创建完成树后,可以读取queries.txt中的查询完成对应的操作。操作有两种:“what is connecting_corridor”意思是查询connecting_corridor有几个子部件。“how many floor hospital”是查询hospital有几个floor。通过读取文件中大的相应操作输出相应的答案。
2.3.2实验步骤
- 首先创建进行实验的需求分析,实验需要用到二叉树存储数据,需要使用链表来实现二叉树,每个节点包括需要有节点名称,数量,总数量,指向孩子的指针和指向兄弟的指针。
- 创建二叉树,首先创建其树根和根的孩子节点,然后从文件中继续读取创建其他节点。
- 从文件“queries.txt”中读取问题,对whatis和howmany两种类型的问题分别进行处理。
- 最后释放二叉树内存。
2.4 图结构的应用
2.4.1实验内容
(1)创建一个图,将文件中图的数据储存到图中,文件“services.txt”中格式如:“Lisbon Madrid 75 450”,表示Lisbon到Madrid距离为450,乘车费用为75。
(2)计算该图的最小生成树。
2.4.2实验步骤
- 需求分析,实验中需要从文件中读取数据并创建图,则先初始化一个边和图结构结构体,根据指导书中的数据,将图的顶点确定,然后从文件中读取数据,将数据存入图中。
边上的权值代表定点之间的路程和花费。图中存在20个顶点,使用20*20的矩阵来存储边和顶点。
图1:边和图的结构体。
- 求最小生成树的方法有普利姆算法和克鲁斯卡尔算法。在实验中我选择了使用普里姆算法,普里姆算法的算法思想是选择点。理解普利姆算法并用普利姆算法完成该问题。
3. 实验环境
操作系统、调试软件名称、版本号,上机地点,机器台号
操作系统:Windows11
调试软件:CodeBlocks
版本号: 20.03-r11983
上机地点:信息楼A407实验室
机器台号:使用个人笔记本电脑
- 实验过程与分析
4.1线性表的应用
4.1.1创建结构体,定义结构体类型
图2:结构体的定义
4.1.2设计插入节点的函数,将创建节点的部分封装成创建节点的函数。使用了头插法,定义了一个指向头结点的指针head用来指向该链表。
图3:插入节点的函数及创建新节点的函数
4.1.3设计打印成绩单的函数,并在主函数中把插入及打印功能实现,测试这两部分功能是否正常
4.1.4设计分析某一门成绩最高分、最低分、平均分的函数,并在主函数中进行实现。使用了while循环遍历链表,并且使用switch选择结构,根据输入不同,输出不同科目的成绩分析。
图6:成绩分析函数(仅截出语文部分,其余部分类似)
4.1.5设计多组测试用例,进行多次测试,最终确定程序无误,实现实验目标。
到此整个项目完成。
4.2栈和队列的应用
4.2.1定义栈的类型,和栈的基本操作函数。
图7:栈节点的定义
4.2.2定义判断字符是否是操作符的函数,判断操作符优先级的函数,以及计算函数(输入两个操作数一个操作符进行运算
图9:判断字符是否是操作数
4.2.3根据前期分析及前面定义的函数和栈,完成程序的主体:将中缀表达式计算。
这里我有两种方法:
- 将中缀表达式转化为后缀表达式,之后利用栈级算后缀表达式。
- 直接计算中缀表达式,使用选择结构分别设计遍历表达式遇到操作数、操作符、括号的不同操作。
这里我使用了第二种方法:
4.2.4将子函数整合到主函数当中
图11:主函数
前面把诸多功能封装在子函数里,主函数更加清晰整洁。
4.3二叉树的应用
4.3.1经分析,实验要求使用二叉树存储该树结构,而树中还存在三叉树,所以只能使用左孩子右兄弟表示法创建树。再者因为需要输出节点共有几个,所以在每个节点中增加变量num和alnum分别表示该设施的数量和总数量。
图12:定义节点结构体
4.3.2在程序中增加节点和查找节点都需要多次遍历树来查找节点,因此定义一个遍历函数。
图13:遍历函数
4.3.3首先解决实验内容(1)中的读取文件并创建树的操作,先创建树的根节点和它的左孩子,然后从根节点开始,读取文件中的内容将其余的信息存入树中。
图14:创建树的代码
4.3.4对于实验内容(2)的解决方案,首先读取每一行中的第一个字符串,然后判断question是“whatis”还是“howmany”,第二个字符串存入name,第三个字符串存入parent,首先是对文件中“whatis”类型问题的解决。通过遍历函数找到name所在的位置,返回到指针p中,之后打印p的所有孩子。
图15:对“whatis”类型问题的解决方案
4.3.5对于“howmany”类型的问题,首先是遍历二叉树找到parent所在位置,返回到指针p中,然后在p的子树中遍历查找name所对应的节点,返回指针q。打印的数据中所求设施的数量可以表示为p对应节点的alnum和q对应节点的alnum的商。
图16:对howmany类型问题的解决。
4.3.6最后,释放二叉树的内存。
图17:释放二叉树
4.4图的应用
4.4.1首先我们从实验指导书中了解到该图有20个顶点,先对该图进行初始化,将20个顶点存入图的顶点中。
图18:对图的初始化
4.4.2然后从文件中读取图的相关数据,存入图中。每次读取文件中的数据,会根据地点名称通过getNodeIndex函数返回顶点对应的索引,通过索引把数据存入图中。
图19:从文件中读取数据存入图中。
4.4.3使用普利姆算法计算该图的最小生成树并打印。首先初始化key数组为无穷大,myset数组为false,parent数组为-1,然后从第一个节点开始构建最小生成树,最后打印最小生成树的边和权值。
图20:普利姆算法实现
5.实验结果总结
5.1线性表的应用
测试用例:
用例1:
学生数量:3
学生成绩:
小明 66 55 77
小罗 33 44 55
小兵 55 66 44
图21:测试结果
测试结果完全正确。
用例2:
学生数量:4
学生成绩:
小明 22 55 88
小罗 33 66 99
小兵 11 44 77
小玲 11 22 33
图22:测试结果
经测试,我所设计的程序达成了实验目标,运用了线性表实现了成绩的统计分析和展示。
5.2栈和队列的应用
测试用例:
用例1:
中缀表达式:2+(3+14*2)-8/2
输出结果:29
图23:用例1测试
用例2:
中缀表达式:32+15/3*2+(3+1)
运算结果:46
图24:用例2测试
经测试,我所设计的程序完全实现了将中缀表达式求值的目的,程序运行稳定,计算结果正确。
5.3二叉树的应用
测试用例:definitions.txt和queries.txt文件
测试结果
图25:测试结果
经测试,程序正常运行,输出结果正确!
5.4图的应用
测试用例:services.txt
测试结果:
图26:测试结果
经测试,程序正常运行,并成功打印了正确的最小生成树,权值和为10600!
5.5实验总结
在这次实验中,我进一步掌握了链表的使用方法,使用链表存储和操作数据更加熟练。但是虽然看起来简单,但在实现过程中还是遇到了一些小问题。通过不断调试代码,寻找错误,不断修改,程序最终才正确运行,满足实验要求。其中一个小问题就是在将链表头指针传入函数中时,函数中的操作并不会改变链表的结构,在查阅资料和自主分析之后,我想出了三种解决方案,其一是将链表的头指针定义为一个全局变量,其二是定义一个指向头指针的指针传入到函数中,其三是传入头指针并且函数返回头指针,这三种方法都可以实现在函数中改变链表的节点的值。
在实验二栈的应用中,我进一步理解了栈的应用方法以及中缀表达式的处理。但是在实现过程中依然遇到了问题。起初我的思路是用一个栈和一个字符串将中缀表达式转化为后缀表达式存储在字符串中,再用栈将计算后缀表达式的值,但实现起来更加复杂繁琐。经过思考,我又想到了使用两个栈,一个存储操作数,一个存储操作符,直接计算中缀表达式的值。
最后完美实现了中缀表达式的求值。
在实验三中,主要应用了左孩子右兄弟表示法,在最初编写代码过程中并不熟练,后来经过网上学习查阅资料实现了二叉树的创建,但是在遍历查找去实现“queries.txt”中的问题时,在递归的使用上出现了很大的问题,不知道在哪里递归和怎么去设计递归,后面经过和同学的探讨以及上网查阅类似的代码,写出了最终的递归查找算法。
在实验四图结构的应用中,通过求无向图的最小生成树,我对于普利姆算法有了更加深刻的认识。在最开始进行实验设计时,我认为我在课堂上已经基本了解了普利姆算法的原理,觉得实现起来也是手到擒来。但是在实际实现过程中遇到了阻力,每一步的原理落实到代码上都不容易,也使我认识到什么是“纸上谈兵”。比如如何表示该顶点已经在生成树的顶点集合中?后面在编写代码的过程中遇到了不少小bug,但是最终一步步调试也得到了解决。最终完成了实验要求,算出了最小生成树。
总之,在这四个实验中,通过对线性表、栈、二叉树、图的学习和应用,我进一步加深了对这些概念的理解,最重要的是熟练了它们的实现。了解了数据结构中一些基本算法的原理。也使我认识到:在知识的学习中,理论学习是基础,实际应用是进一步理解理论和应用理论的必要过程。实验应用不仅仅可以锻炼逻辑思维,加深知识理解,对于以后的学习也是非常有益的。