尧图精选

运动会分数统计实验:结构体与线性表实战指南

🕒 发布时间:2026/10/2 18:09:55 📁 来源:尧图网络
简介这份运动会分数统计实验报告面向数据结构与软件设计课程的初学者及需要完成课程设计的学生围绕线性链表、结构体与模块化函数设计解决多学院、多项目比赛成绩录入与团体总分统计的实际问题。压缩包内仅含1个docx文档约91KB完整呈现实验目的、要求、内容、数据定义、函数设计、测试结果、调试分析与实验总结并附有C源程序。报告以n4、m3、w2为测试用例详细讲解InitSCH初始化、input输入、mark算分、output输出四个核心函数的实现思路涵盖分支判断、循环遍历、递归重输、指针传参与健壮性检查等关键细节。读者可借此掌握线性链表的建立与遍历、结构体封装运动员与学院信息、按名次7、5、3、2、1计分并生成男女团体总分报表的完整流程同时获得指针使用、属性初始化、错误处理等排错经验。目前已有390人学习下载适合作为数据结构实验参考与课程报告模板。1. 运动会分数统计实验报告从结构体到线性表的一次完整落地校运会结束那天裁判组丢过来一叠手写成绩单要求当天出团体总分和名次。手工算七个院系、十几个项目、还有破纪录加分算到一半就乱了。这类运动会分数统计任务本质是一个典型的数据结构入门实战用结构体描述一条成绩记录用线性表组织所有记录再靠分支结构处理名次、加分、并列这些规则。它出现在几乎所有《数据结构》课程的实验报告里也是王道408、严蔚敏版教材反复强调的基础题型。这篇笔记不讲空泛概念而是把结构体定义、线性表存储、输入输出、分支判断这几块拆开给出能直接抄进实验报告的代码骨架顺带说清楚评分老师真正在意的几个点。适合正在赶数据结构实验、或者想把C语言结构体真正用起来的人。2. 结构体怎么定义把一条成绩记录拆成字段2.1 先想清楚一条记录里到底有什么很多人一上来就写struct student结果写到一半发现缺字段回头改结构体牵一发动全身。血泪经验是动手前先把一条成绩记录在纸上列全。运动会分数统计里一条记录通常包含院系编号、院系名称、项目编号、项目名称、名次、得分、是否破纪录。如果按院系-项目二维来存还要考虑一个院系参加多个项目的情况。这里有个选型分歧是按每个运动员一条记录还是按每个院系每个项目一条记录前者贴近原始成绩单后者贴近最终统计需求。我一般选前者因为原始数据最不容易丢统计时再聚合。结构体字段命名要能自解释别用a、b、c评分老师第一眼就看命名。/* 单条成绩记录一名运动员在一个项目上的成绩 */ typedef struct { int deptId; /* 院系编号如 1~7 */ char deptName[32]; /* 院系名称中文注意编码 */ int itemId; /* 项目编号 */ char itemName[32]; /* 项目名称 */ int rank; /* 名次1 表示第一 */ int score; /* 本项目得分 */ int isRecord; /* 是否破纪录0 否1 是 */ } ScoreRecord;逻辑说明typedef struct {...} ScoreRecord;是C语言里定义结构体类型的标准写法省去每次写struct ScoreRecord。字段用定长数组存字符串避免指针带来的内存管理麻烦实验报告阶段够用。isRecord用 int 而不是 bool是为了兼容老编译器很多学校机房还是VC6。参数说明deptName[32]的 32 是经验值中文按UTF-8一个汉字3字节算能放10个汉字够用如果机房用GBK编码一个汉字2字节更宽松。rank和score分开存是因为破纪录加分会让名次对应的基础分和最终得分不一致合并成一个字段后面必翻车。2.2 结构体初始化与内存对齐的坑结构体定义完紧接着就是初始化。C语言里初始化结构体有三种常见写法实验报告里推荐用指定初始化器designated initializer可读性最好ScoreRecord r1 {1, 计算机学院, 101, 男子100米, 1, 9, 0}; ScoreRecord r2 {.deptId 2, .deptName 外国语学院, .itemId 101, .itemName 男子100米, .rank 2, .score 7, .isRecord 0};第一种按顺序赋值字段一多就容易错位第二种指定字段名顺序无关改结构体也不容易出错。我一般强制用第二种。内存对齐是另一个高频踩坑点。上面这个结构体int占4字节char[32]占32字节看起来总大小是 432432444 84 字节但实际sizeof(ScoreRecord)很可能是 88。原因是编译器会在deptName后面补齐到4字节边界。这个知识点在408里考在实验报告里如果老师较真也会问。想验证printf(sizeof(ScoreRecord) %lu\n, sizeof(ScoreRecord));如果输出 88 而不是 84就是对齐在起作用。解决办法有两个一是调整字段顺序把char数组放最后二是用#pragma pack(1)强制1字节对齐但会牺牲访问速度。实验报告里我一般不改只在报告里说明存在内存对齐实际大小以 sizeof 为准反而显得你懂。3. 线性表怎么组织顺序表还是链表3.1 顺序表存储数组 计数器成绩记录条数在运动会场景下是可预估的几百条顶天所以顺序表数组是最省事的选择。核心是一个结构体数组加一个当前长度#define MAX_RECORDS 512 typedef struct { ScoreRecord data[MAX_RECORDS]; int length; /* 当前实际记录数 */ } SeqList; /* 追加一条记录返回 0 成功-1 失败 */ int append(SeqList *list, ScoreRecord r) { if (list-length MAX_RECORDS) { return -1; /* 表满拒绝写入 */ } list-data[list-length] r; list-length; return 0; }逻辑说明SeqList把数组和长度打包在一起传参时只传一个指针比裸数组加全局变量干净得多。append先判满再写入这是顺序表的基本纪律漏了判满就是经典越界。参数说明MAX_RECORDS设 512 是留了余量实际运动会记录数一般不超过 300。如果学校规模大改成 1024。length从 0 开始始终指向下一个空位所以有效下标是0 ~ length-1遍历时写for (i 0; i list-length; i)别写成。顺序表的优势是随机访问快统计总分时直接遍历求和O(n) 一次搞定。劣势是中间插入删除要搬数据但运动会统计基本只追加不删除所以这个劣势用不上。3.2 链表存储什么时候才值得用如果题目明确要求动态增删或者记录数完全不可预估才考虑链表。链表节点定义typedef struct Node { ScoreRecord data; struct Node *next; } Node; /* 头插法建表返回新头指针 */ Node* insertHead(Node *head, ScoreRecord r) { Node *p (Node*)malloc(sizeof(Node)); if (p NULL) return head; /* 内存分配失败原样返回 */ p-data r; p-next head; return p; }逻辑说明头插法每次把新节点放到最前面代码最短但插入顺序和遍历顺序相反。如果实验报告要求按输入顺序输出就得用尾插法多维护一个尾指针。参数说明malloc后必须判空机房内存小的时候真会失败。链表节点里的data是整个ScoreRecord结构体不是指针这样节点生命周期自包含不用额外管理记录内存。选型建议运动会分数统计这个题目90%的情况用顺序表就够了代码短、调试容易、评分老师看着清晰。链表留给学生成绩管理系统那种要求增删改查齐全的题目。别为了炫技上链表结果指针指错调试两小时得不偿失。4. 输入输出与分支结构统计逻辑怎么写4.1 从文件读成绩fscanf 读结构体实验报告的数据来源通常是文件用fscanf按格式读。假设成绩文件score.txt每行是院系编号 院系名 项目编号 项目名 名次 是否破纪录。#include stdio.h int loadFromFile(SeqList *list, const char *path) { FILE *fp fopen(path, r); if (fp NULL) { printf(打开文件失败%s\n, path); return -1; } ScoreRecord r; /* 注意%s 遇到空白就停中文名不能带空格 */ while (fscanf(fp, %d %s %d %s %d %d, r.deptId, r.deptName, r.itemId, r.itemName, r.rank, r.isRecord) 6) { r.score baseScore(r.rank); /* 得分由名次换算 */ if (append(list, r) ! 0) { printf(记录已满停止读取\n); break; } } fclose(fp); return 0; }逻辑说明fscanf的返回值是成功匹配并赋值的字段个数这里期望 6 个所以用 6作为循环条件读到文件尾或格式不符就退出。这是fscanf读结构体的标准套路比feof判断更可靠因为feof会在最后一次读取后才置位容易多读一条。参数说明%s读字符串时遇到空格、换行就停所以院系名和项目名里不能有空格。如果实际数据有空格比如男子 100米得改用%[^ ]之类的格式或者干脆用fgets读整行再sscanf解析。baseScore是名次换分的辅助函数见下一节。4.2 名次换分与破纪录加分分支结构的主战场分数统计的核心规则一般是这样第1名9分第2名7分第3名6分第4名5分第5名4分第6名3分第7名2分第8名1分破纪录额外加5分。用switch或if-else都行switch更清晰/* 名次换算基础分名次超出 8 返回 0 */ int baseScore(int rank) { switch (rank) { case 1: return 9; case 2: return 7; case 3: return 6; case 4: return 5; case 5: return 4; case 6: return 3; case 7: return 2; case 8: return 1; default: return 0; /* 无名次或名次无效 */ } } /* 计算最终得分基础分 破纪录加分 */ int finalScore(int rank, int isRecord) { int s baseScore(rank); if (isRecord 1) { s 5; } return s; }逻辑说明switch里每个case直接return省掉break这是常见简写。default返回 0处理名次为 0 或大于 8 的异常数据避免脏数据污染总分。参数说明加分值 5 是示例实际规则看题目。如果题目说破纪录加 5 分且名次分翻倍那finalScore里就要先判断再乘顺序不能反。分支结构最容易出的错是漏掉default导致异常输入返回随机值。4.3 按院系汇总总分并排名有了单条记录的得分接下来按院系聚合。用一个辅助数组存每个院系的总分再排序#define MAX_DEPT 16 typedef struct { int deptId; char deptName[32]; int total; } DeptTotal; /* 汇总各院系总分 */ int sumByDept(SeqList *list, DeptTotal depts[], int *deptCount) { int i, j, n 0; for (i 0; i list-length; i) { ScoreRecord *r list-data[i]; /* 在已收集的院系里找 */ for (j 0; j n; j) { if (depts[j].deptId r-deptId) break; } if (j n) { /* 没找到新增一个院系 */ depts[n].deptId r-deptId; strcpy(depts[n].deptName, r-deptName); depts[n].total 0; n; } depts[j].total r-score; } *deptCount n; return 0; }逻辑说明外层遍历所有记录内层在已收集院系里线性查找。找到就累加没找到就新增。这是分组聚合最朴素的写法时间复杂度 O(n*m)n 是记录数m 是院系数运动会场景下完全够用。参数说明depts数组由调用方分配deptCount用指针传出实际院系数。strcpy复制院系名前提是目标数组够大这里 32 字节和源字段一致安全。如果院系名可能超长改用strncpy并手动补\0。汇总完再按total降序排序冒泡或选择排序都行代码短实验报告里够用。排序后输出名次注意处理总分相同的情况——并列名次是运动会统计的经典需求处理方式是分数相同则名次相同下一个名次跳号。5. 避坑与排查实验报告里最容易翻车的五处5.1 中文乱码文件编码和终端编码不一致现象从文件读进来的院系名打印出来是乱码或者strcmp比较院系名永远不相等。原因源文件、数据文件、终端三者的编码不一致。机房常见组合是源文件 GBK、数据文件 UTF-8或者反过来。C语言按字节比较字符串编码不同字节序列就不同。解决统一用同一种编码。最稳的办法是数据文件里院系名用编号代替输出时再映射成中文或者用setlocale配合宽字符但实验报告阶段没必要。我一般直接在报告里注明数据文件与源文件同为 GBK 编码避免争议。5.2 fscanf 读字符串截断名字里带空格现象项目名男子 100米读进来只剩男子后面的100米被当成下一个字段导致后续所有字段错位。原因%s遇到空白字符就停止这是格式串的固有行为。解决改用fgets读整行再用sscanf按固定分隔符解析或者约定数据里不含空格项目名写成男子100米。实验报告里推荐后者简单且不易错。5.3 数组越界length 和下标混用现象程序偶尔崩溃或者最后一条记录统计不到。原因遍历时写成i list-length多访问了一个未初始化元素或者append里忘了判满写越界。解决牢记length是元素个数有效下标是0 ~ length-1。所有循环统一写i list-length。append第一行就判满。这两个习惯能消灭 80% 的越界问题。5.4 总分累加用了基础分而非最终分现象破纪录的运动员所在院系总分偏低。原因汇总时累加的是baseScore(rank)忘了加破纪录的 5 分。解决在读取阶段就把r.score finalScore(r.rank, r.isRecord)算好存进记录汇总时直接累加r.score。别在汇总阶段再算分逻辑分散容易漏。5.5 并列名次处理错误跳号还是连号现象两个院系总分相同输出名次一个第2一个第3但题目要求并列第2。原因排序后直接按下标赋名次没做相等判断。解决排序后遍历若当前总分等于前一个名次沿用前一个否则名次等于当前下标1。这样并列后会自动跳号符合大多数运动会规则。如果题目要求连号就改成相等时名次不变但下一个仍递增具体看题目描述别想当然。6. 把实验报告写扎实验证方法与一个提分技巧代码能跑只是及格线实验报告想拿高分得让老师看到你验证过、思考过。我一般会做三件事。第一构造边界测试数据。准备一个edge.txt里面包含名次为 0 的异常记录、名次大于 8 的记录、破纪录记录、两个院系总分相同的情况、只有一条记录的情况。跑一遍把输出截图贴进报告旁边注明验证了 default 分支和并列名次逻辑。这比空谈程序正确有说服力得多。第二打印中间结果做交叉验证。在汇总函数里临时加一行printf(dept %d total%d\n, depts[j].deptId, depts[j].total);把每个院系的总分打出来手工用计算器核对一两个院系。核对通过后把这行注释掉。这个习惯能帮你快速定位是读取错了还是汇总错了。第三在报告里写清楚时间复杂度和空间复杂度。读取是 O(n)汇总最坏 O(n*m)排序 O(m²)空间 O(nm)。这些分析不用长篇大论几行字就够但能体现你有数据结构意识而不是只会写代码。一个提分技巧把结构体字段设计单独写一小节说明为什么rank和score要分开、为什么isRecord用 int。老师看实验报告最烦的是只贴代码不讲设计。你把这几个字段的取舍讲清楚等于告诉老师我理解结构体是在建模不是在堆变量。最后说个我自己的习惯每次写完这类统计程序我都会拿真实数据跑一遍哪怕数据只有十几条。因为真实数据里总有你没预料到的情况——名次重复、院系名带括号、某条记录缺字段。跑一次真实数据比写十组测试用例都管用。这个习惯从数据结构实验一直保持到现在希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →