浙江理工大学数据结构课程设计
实验指导书
前言
《数据结构》课程是计算机科学与技术专业,信息与计算科学专业和电子信息科学与技术专业的重要专业基础课程之一。
希望同学们能够充分利用实验条件,认真完成实验,从实验中得到应有的锻炼和培养。
本书是为配合《数据结构实验教学大纲》而编写的上机指导,其目的是使学生消化理论知识,加深对讲授内容的理解,尤其是一些算法的实现及其应用,培养学生独立编程和调试程序的能力,使学生对数据结构有更深刻的认识。
希望同学们在使用本实验指导书及进行实验的过程中,能够帮助我们不断地发现问题,并提出建议。
一、选题
1~2人一组,在下面题目中任选一题实现,要求程序有界面。按照要求完成课程设计报告。
1.最少换乘次数、最少通行时间问题
【问题描述】设某城市有N个车站,并有M条公交线路连接这些车站。设这些公交车站都是单向的,这N个车站被顺序编号为0~N-1。编程序,输入该城市的公交线路,车站个数,站与站之间的运行时间以及各公交线路上的各站编号。
【实现要求】输入起点和终点,给出乘公交车的最少换乘次数或最少通行时间,给出具体乘车方案,例如坐几路车,坐几站,到哪一站换乘几路车。车站和公交线路信息存在文档中。
【提高】输入每个线路最早发车时间、发车间隔,考虑换乘时的等候时间,给出最少通行时间的方案。
2.哈夫曼编码
【问题描述】从文本文件中读取要传输的字符,设计一个利用哈夫曼算法的编码系统,为其编码并在接收端进行译码。
【实现要求】将需要传输的数据存放在数据文件data.txt中,读入数据文件并为其编码,将编码后的内容存入文件code.txt中,保存huffman树,读入code.txt和huffman树,译码,并将译码后的内容输出在屏幕上。
【提高】对任意的二进制文件,例如图像文件,进行哈夫曼编码、译码。
3.航空客运订票系统
【问题描述】航空客运订票的业务活动包括:查询航线、客票预订和办理退票等,上述业务可以借助计算机来完成。
【实现要求】
(1)每条航线所涉及的信息有:终点站名、航班号、KVL号、星期几、乘员定额、总票量、已订票的客户名单(包括姓名、订票量、舱位等级1,2或3)以及等候替补的客户名单(包括姓名、所需票层);
(2)作为示意系统,全部数据可以只放在内存中,或者存入文档;
(3)系统能实现的操作和功能如下:
①查询航线:根据旅客提出的终点站名输出下列信息:航班号、飞机号、星期几飞行,最近一天航班的日期和余票额。
②承办订票业务:根据客户提出的要求(航班号、订票数额)查询该航班票额情况,若尚有余票,则为客户办理订票手续,输出座位号;若已满员或余票额少于订票额,则需重新问客户要求。若需要,可登记排队候补;
③承办退票业务:根据客户提供的情况(日期、航班),为客户办理退票手续,然后查询该航班是否有人排队候补,首先询问排在第一的客户,若所退票额能满足他的要求,则为力、理订票手续,否则依次询问其它排队候补的客户。
【提高】界面设计,一个完整的系统。
4.通讯录
【问题描述】.设计散列表,实现通讯录查找系统
【实现要求】
(1)设每个记录有下列数据项,电话号码,用户名地址。
(2)从键盘输入的记录分别以电话号码为关键词建立散列表。
(3)采用线性探测再散列法解决冲突。
(4)查找并显示给定电话号码的记录。
(5)通讯录信息文件保存。
【提高】要求人机界面友好,使用图形化界面。
5.全国交通咨询模拟
【问题描述】
出于不同目的的旅客对交通工具有不同的要求。例如,因公出差的旅客希望在旅途中的时间尽可能短,专门旅游的游客则期望旅费尽可能省,而老年旅客则要求中转次数最少。编制一个全国城市的交通咨询程序,为旅客提供两种或三种最优决策的交通咨询。
【实现要求】
(1)提供对城市信息进行编辑(如:添加或删除)的功能。
(2)城市之间有两种交通工具:火车和飞机。提供对列车时刻表和飞机航班进行编辑、增设或删除)的功能。
(3)提供一种最优决策:最快到达或最省钱到达,全程只考虑一种交通工具。
(4)旅途中耗费的总时间应该包括中转站的等候时间。
(5)咨询以用户和计算机的对话方式进行。由用户输入起始站、终点站、最优决策原则和交通工具,输出信息为:最快需要多长时间才能到达或者最少需要多少旅费才能到达,并详细说明依次于何时乘坐哪一趟列车或哪一次班机到何地。
【提高】提供两种最优决策,最快到达和最省钱到达。
6.文章编辑
【问题描述】 编程序,对给定的文章进行编辑。
【实现要求】
输入一段文字。
(1)分别统计出其中英文字母数和空格数及整篇文章总字数;
(2)统计某一字符串在文章中出现的次数,输出该字符串出现的位置(第几行,第几个单词),如果有GUI界面,查找的字符串高亮显示。
(3)删除某一字符串,并将后面的字符前移。
【提高】程序可以对文字分页,一页最多20行,超出20行分页。可分页显示,用PageUp,PageDown或其他指定按键进行翻页操作。
7.自拟题目
经指导教师同意,可选择自拟题目。
二、要求
(1)选题需求分析。重点陈述选题意义、程序设计的任务,以及模块划分。
(2)概要设计。说明本程序中用到的所有抽象数据类型的定义、主程序流程以及各程序模块之间的层次调用关系。
(3)详细设计。实现概要设计中定义的所有数据类型,对每个操作只需写出伪代码算法对主程序和其他模块也只需写出伪代码算法,绘制系统的功能结构图和业务流程图。
(4)调试分析。对算法进行时空复杂度分析,并总结经验和体会。
(5)用户使用说明。详细列出用户如何操作。
(6)测试结果。
测试数据:要求使用1、全部合法数据;2、整体非法数据;3、局部非法数据。
进行程序测试,以保证程序的稳定,附结果截图。
(7)附录。附关键代码,有注释,注明每个人所完成的模块及分工百分比。
最终在云班课上提交报告、源程序、答辩记录表、视频(<50M)。
验收将分为两个部分。第一部分是上机操作,包括检查程序运行和即时提问。第二部分是提交书面的课程设计报告。此外,针对以前教学中出现的问题,整个过程将采用阶段检查方式,每个阶段任务都将应当在规定的时间内完成,过期视为未完成该任务,不计成绩。以避免期末集中检查方式产生的诸多不良问题,希望同学们抓紧时间,合理安排,认真完成。
三、计划安排
时间 | 地点 | 工作内容 | 指导教师 | |
6.21 | 上午 | 10-414 | 完成选题,需求分析 | 胡洁、郭奕亿 |
下午 | 10-414 | 提交选题、需求分析文档 | 胡洁、郭奕亿 | |
6.22 | 上午 | 10-414 | 基本设计 | 胡洁、郭奕亿 |
下午 | 10-414 | 完成基本设计文档 | 胡洁、郭奕亿 | |
6.23 | 上午 | 10-414 | 编写代码 | 胡洁、郭奕亿 |
下午 | 10-414 | 编写代码 | 胡洁、郭奕亿 | |
6.24 | 上午 | 10-414 | 程序测试 | 胡洁、郭奕亿 |
下午 | 10-414 | 程序测试 | 胡洁、郭奕亿 | |
6.25 | 上午 | 10-414 | 撰写课程设计报告,答辩 | 胡洁、郭奕亿 |
下午 | 10-414 | 撰写课程设计报告,答辩 | 胡洁、郭奕亿 |
四、成绩考核标准
本课程设计的评价由两部分组成,包括答辩(50%)(包括程序演示(30%),回答教师提问(20%))和 课程设计报告(50%)。指导教师根据每个学生必做实验和选做完成情况、实验结果的正确性、选做实验的数量和完成情况进行评分。良好以上要求是独立完成,熟悉源代码,能快速回答老师提问,报告内容详实规范。
浙江理工大学数据结构课程设计
20计算机类(5)班
杨正龙
一、选题(阐述选题依据和选题意义)
1.选题依据
在6个不同的题目中,经过对每道题进行分析,我们小组最终选择了第4题通讯录。因为其题目比较清晰,指向性较为明确,并且可实现性和可操作性都比较高。除此之外,因为有了链表实现类似通讯录结构的基础,通过散列表实现本课题可以让我们更快的入手。
2.选题意义
通讯录是我们生活中必不可少的一种应用。以通讯录为选题,将来可以将这个应用移植到其他平台,并完成UI界面的美化,有一定的现实应用价值。
二、需求分析
1.问题描述
【问题描述】
设计散列表,实现通讯录查找系统
【实现要求】
(1)设每个记录有下列数据项,电话号码,用户名地址。
(2)从键盘输入的记录分别以电话号码为关键词建立散列表。
(3)采用线性探测再散列法解决冲突。
(4)查找并显示给定电话号码的记录。
(5)通讯录信息文件保存。
【提高】
要求人机界面友好,使用图形化界面。
2.基本要求
(1)输入的形式和输入值的范围:
在dos界面中,用1-7的数字分别表示:添加用户信息、打印所有用户信息、以电话号码建表、查找并显示给定电话号码的记录、清空、保存、退出程序;并且在完成操作后再次给出提示,引导用户继续进行下一步操作;
在GUI界面中,用点击按钮的方式实现上述操作。
输入值的范围在建表之后给出提示。
(2)输出的形式
在选择“读取所有用户信息”之后,分别显示
第n个用户信息:XXX
姓名:XXX
电话号码:XXX
联系地址:XXX
(3) 程序所能达到的功能
程序可以完成:添加用户信息、打印所有用户信息、以电话号码建表、查找并显示给定电话号码的记录、清空、保存、退出程序7项功能,并用图形化的方式表示。
三、基本设计
1.系统结构图
主函数(main):
2.业务流程图
getin(a):
ShowInformation(a):
CreatHash(H,a):
SearchHash(H,c):
Save(a):
Hash(): Collision(p,c):
3.数据结构的设计
typedef struct //记录
{
NAME name;
NAME tel;
NAME add;
} Record;
typedef struct//散列表
{
Record* elem[HASHSIZE]; //数据元素存储基址
int count; //当前数据元素个数
int size; //当前容量
} HashTable;
四、程序代码与说明
hash.h:
#include <stdio.h>
#include<stdlib.h>
#include <string>
#define MAXSIZE 20 //电话薄记录数量
#define MAX_SIZE 20 //人名的最大长度
#define HASHSIZE 50 //定义表长
#define ok 1
#define error -1
#define LEN sizeof(HashTable)
typedef int Status;
typedef char NAME[MAX_SIZE];
typedef struct //记录
{
NAME name;
NAME tel;
NAME add;
} Record;
typedef struct
{//散列表
Record* elem[HASHSIZE]; //数据元素存储基址
int count; //当前数据元素个数
int size; //当前容量
} HashTable;
Status eq(NAME x, NAME y)
{
//关键字比较,相等返回1;否则返回-1
if (strcmp(x, y) == 0)
return ok;
else return error;
}
Status NUM_BER; //记录的个数
main.cpp:
#include "hash.h"
void getin(Record* a);
//键盘输入各人的信息
void ShowInformation(Record* a);
//显示输入的用户信息
int Hash_Tel(NAME str);
//电话号码建表的散列函数
Status collision(int p, int& c);
//冲突处理函数,采用线性探测再散列法解决冲突
void CreateHash_Tel(HashTable* H, Record* a);
//建表,以电话号码为关键字,建立相应的散列表,并解决相应的冲突
void SearchHash_Tel(HashTable* H, int& c);
//在通讯录里查找电话号码关键字,若查找成功,显示信息并显示冲突次数
void Save(Record* a);
//保存数据
long fold(NAME s);
//人名的折叠处理
int Hash_Name(NAME str);
//姓名建表的散列函数
Status collision_double(int p, int& c);
//冲突处理函数,采用二次探测再散列法解决冲突
void CreateHash_Name(HashTable* H, Record* a);
//建表,以人的姓名为关键字,建立相应的散列表,并解决相应的冲突
void SearchHash_Name(HashTable* H, int& c);
//在通讯录里查找姓名关键字,若查找成功,显示信息并显示冲突次数
int main(void)
{
int c = 1;
HashTable* H;
H = (HashTable*)malloc(LEN);
for (int i = 0; i < HASHSIZE; i++)
H->elem[i] = NULL;
H->size = HASHSIZE;
H->count = 0;
Record a[MAXSIZE];
printf("欢迎使用通讯录查找系统:\n");
printf("1.添加用户信息\n");
printf("2.读取所有用户信息\n");
printf("3.以电话号码建表\n");
printf("4.以电话号码查找\n");
printf("5.以姓名建表\n");
printf("6.以姓名查找\n");
printf("7.通讯录信息文件保存\n");
printf("0.退出程序\n");
while (1)
{
int num;
printf("\n请输入您的操作:\n");
scanf("%d", &num);
switch (num)
{
case 1:
getin(a);
break;
case 2:
ShowInformation(a);
break;
case 3:
CreateHash_Tel(H, a);
break;
case 4:
c = 0;
SearchHash_Tel(H, c);
break;
case 5:
CreateHash_Name(H, a);
break;
case 6:
c = 0;
SearchHash_Name(H, c);
break;
case 7:
Save(a);
break;
case 0:
return 0;
break;
default:
printf("输入错误,请重新输入!\n");
}
}
system("pause");
return 0;
}
void getin(Record* a)//键盘输入各人的信息
{
int num;
int i;
printf("输入要添加的个数:\n");
scanf("%d", &num);
for (i = NUM_BER; i < NUM_BER+num; i++)
{
printf("请输入第%d个记录的用户名:\n", i + 1);
scanf("%s", a[i].name);
printf("请输入%d个记录的电话号码:\n", i + 1);
scanf("%s", a[i].tel);
printf("请输入第%d个记录的地址:\n", i + 1);
scanf("%s", a[i].add);
}
NUM_BER += num;
}
void ShowInformation(Record* a)//显示输入的用户信息
{
int i;
for (i = 0; i < NUM_BER; i++)
printf("\n第%d个用户信息:\n 姓名:%s\n 电话号码:%s\n 地址:%s\n", i + 1, a[i].name, a[i].tel, a[i].add);
}
int Hash_Tel(NAME str)//电话号码建表的散列函数
{
long n;
int m;
n = atoi(str); //把字符串转换成整型的函数.
m = n % HASHSIZE; //用除留余数法构造散列函数
return m; //并返回模值
}
Status collision(int p, int& c)//冲突处理函数,采用线性探测再散列法解决冲突
{
int i, q;
i = c / 2 + 1;
while (i < HASHSIZE)
{
c++;
q = (p + i) % HASHSIZE;
if (q >= 0) return q;
else i = c / 2 + 1;
}
return error;
}
void CreateHash_Tel(HashTable* H, Record* a)//建表,以电话号码为关键字,建立相应的散列表,并解决相应的冲突
{
int i, p = -1, c, pp;
for (i = 0; i < NUM_BER; i++)
{
c = 0;
p = Hash_Tel(a[i].tel);
pp = p;
while (H->elem[pp] != NULL)
{
pp = collision(p, c);
if (pp < 0)
{
printf("第%d记录无法解决冲突", i + 1); //显示冲突次数
continue;
} //无法解决冲突
}
H->elem[pp] = &(a[i]); //求得散列地址,将信息存入
H->count++;
printf("第%d个记录冲突次数为%d。\n", i + 1, c); //显示冲突次数
}
printf("\n建表完成!\n此散列表容量为%d,当前表内已使用的数据个数为%d.\n", HASHSIZE, H->count);
}
void SearchHash_Tel(HashTable* H, int& c)//在通讯录里查找电话号码关键字,若查找成功,显示信息。c用来记录冲突次数,查找成功时显示冲突次数
{
NAME tele;
printf("\n请输入要查找记录的电话号码:\n");
scanf("%s", tele);
int p, pp;
p = Hash_Tel(tele);
pp = p;
while ((H->elem[pp] != NULL) && (eq(tele, H->elem[pp]->tel) == -1))
pp = collision(p, c);
if (H->elem[pp] != NULL && eq(tele, H->elem[pp]->tel) == 1)
{
printf("\n查找成功!\n查找过程中通过线性探测再散列法解决冲突次数为%d.以下是您需要要查找的信息:\n", c);
printf("姓名:%s\n电话号码:%s\n联系地址:%s\n", H->elem[pp]->name, H->elem[pp]->tel, H->elem[pp]->add);
}
else printf("\n此人不存在,查找不成功!\n");
}
void Save(Record* a)//保存数据
{
FILE* fp;
if ((fp = fopen("data.txt", "w")) == NULL)
{
printf("不能打开此文件,请按任意键退出\n");
exit(1);
}
int i;
fprintf(fp, "序号 姓名 电话 地址\n");
for (i = 0; i < NUM_BER; i++)
fprintf(fp, "%d %s %s %s\n", i + 1, a[i].name, a[i].tel, a[i].add);
fclose(fp);
printf("保存成功!\n");
}
long fold(NAME s)
{//人名的折叠处理
char* p;
long sum = 0;
NAME ss;
strcpy(ss, s); //复制字符串,不改变原字符串的大小写
strupr(ss); //将字符串ss转换为大写形式
p = ss;
while (*p != '\0')
sum += *p++;
return sum;
}
int Hash_Name(NAME str)
{//姓名建表的散列函数
long n;
int m;
n = fold(str); //先将用户名进行折叠处理
m = n % HASHSIZE; //折叠处理后的数,用除留余数法构造散列函数
return m; //并返回模值
}
Status collision_double(int p, int& c)
{//冲突处理函数,采用二次探测再散列法解决冲突
int i, q;
i = c / 2 + 1;
while (i < HASHSIZE)
{
if (c % 2 == 0)
{
c++;
q = (p + i * i) % HASHSIZE;
if (q >= 0) return q;
else i = c / 2 + 1;
}
else
{
q = (p - i * i) % HASHSIZE;
c++;
if (q >= 0) return q;
else i = c / 2 + 1;
}
}
return error;
}
void CreateHash_Name(HashTable* H, Record* a)
{//建表,以人的姓名为关键字,建立相应的散列表,并解决相应的冲突
int i, p = -1, c, pp;
for (i = 0; i < NUM_BER; i++)
{
c = 0;
p = Hash_Name(a[i].name);
pp = p;
while (H->elem[pp] != NULL)
{
pp = collision_double(p, c);
if (pp < 0)
{
printf("第%d记录无法解决冲突", i + 1); //需要显示冲突次数时输出
continue;
} //无法解决冲突
}
H->elem[pp] = &(a[i]); //求得散列地址,将信息存入
H->count++;
printf("第%d个记录冲突次数为%d。\n", i + 1, c); //需要显示冲突次数时输出
}
printf("\n建表完成!\n此散列表容量为%d,当前表内已使用的数据个数为%d.\n", HASHSIZE, H->count);
}
void SearchHash_Name(HashTable* H, int& c)
{//在通讯录里查找姓名关键字,若查找成功,显示信息。c用来显示冲突次数
NAME str;
printf("\n请输入要查找记录的姓名:\n");
scanf("%s", str);
int p, pp;
p = Hash_Name(str);
pp = p;
while ((H->elem[pp] != NULL) && (eq(str, H->elem[pp]->name) == -1))
pp = collision_double(p, c);
if (H->elem[pp] != NULL && eq(str, H->elem[pp]->name) == 1)
{
printf("\n查找成功!\n查找过程中通过二次探测再散列法解决冲突次数为%d.以下是您需要要查找的信息:\n", c);
printf("姓 名:%s\n电话号码:%s\n联系地址:%s\n", H->elem[pp]->name, H->elem[pp]->tel, H->elem[pp]->add);
}
else printf("\n此人不存在,查找不成功!\n");
}
五、运行结果与分析
六、心得与体会
在这次的课程设计中,我学到了很多,也收获了很多。通过课程设计,我首先复习了哈希查找的相关知识,并且通过编程切实落实了这一部分的知识。此外,我们还通过课外与网络的学习,学到了一些c#和c++的相关知识并完成了图形化界面的作品并放在了视频展示中,虽然没有在答辩中展示,但是我们切切实实的在这次的课程设计中学到了新东西,这是在平时的学习中不能比拟的。除此之外,在心得方面,我发现理论学习和实际编写代码还是有很大的区别。对书上的代码有理解和掌握但是不代表自己就能写出来。并且在写的时候bug不断,需要一直不断地修改修改。就以遍历表为例,在全局变量,主函数中的部分变量和分函数中的部分变量的选择上就进行了多次的修改,最终选择设定一个可变变量先继承宏定义的值再进行修改,用这种方式完成了遍历表的函数封装。总之,感谢老师一学期来的辛勤付出,使我学会了数据结构这门重要性不言而喻的专业课。我会在接下来的学习中继续使用数据结构的知识,对计算机这一专业进行进一步的深入学习。