简介:本资源为南京邮电大学《数据结构》课程实验一的完整实验报告,面向计算机相关专业学生及正在学习线性表与多项式运算的初学者。报告围绕顺序表与带表头单链表两种存储结构展开,涵盖初始化、查找、插入、删除、输出、撤销等基本运算,并进一步实现一元多项式的创建、输出、相加与相乘,配有核心C源代码、详细注释及时间复杂度分析。压缩包内共1个docx文档,约444KB,内容按实验目的、实验环境、算法设计、算法实现与分析、实验结果与结论等模块组织,结构完整、条理清晰。目前已有774人学习下载,适合需要参考实验报告格式、理解线性表应用或完成同类课程作业的读者借鉴使用。
1. 南京邮电大学数据结构实验一:线性表基本运算与多项式算术运算到底在练什么
如果你正在搜“南京邮电大学数据结构实验一”,大概率是刚拿到实验任务书,看到“线性表的基本运算”和“多项式的算术运算”两个词,心里有点发虚——顺序表、单链表这些概念课上听过,但真让你从零写一个能跑的多项式加法,又不知道从哪下手。这个实验的核心其实就两件事:第一,用 C 语言把线性表的两种存储结构(顺序表和单链表)的基本操作亲手实现一遍;第二,把线性表套进一个真实场景——一元多项式的加法、减法、乘法运算,让你体会“数据结构选得好,算法写起来有多顺”。它适合刚学完线性表章节、需要把课本伪代码变成可编译代码的本科生,也适合考研 408 复习到线性表、想找个小项目练手的同学。说白了,这个实验不是让你造火箭,而是逼你把ListInsert、ListDelete、GetElem这些函数真正写对、写稳,再拿多项式运算当试金石。
2. 顺序表和单链表:先把存储结构选明白再动手
2.1 顺序表与单链表的本质差异
顺序表用一段连续内存存数据,逻辑相邻的元素物理上也相邻,所以按下标访问是 O(1),但插入和删除平均要移动一半元素,代价是 O(n)。单链表每个节点单独分配内存,靠指针串起来,访问第 i 个元素必须从头遍历,代价 O(n),但插入删除只要改指针,不需要搬数据。这个实验里,多项式运算用哪种结构都能做,但体验完全不同:顺序表做多项式加法,你需要额外开一个结果数组,按指数归并;单链表做加法,可以直接在链表上归并节点,空间利用率更高,代码也更“数据结构味”。我一般建议两个都实现一遍,顺序表练基本功,单链表练指针操作,实验报告里对比分析也有素材。
2.2 顺序表基本运算的最小实现
先看顺序表的结构定义和几个核心操作。下面这段代码可以直接编译运行,重点看ListInsert和ListDelete里对边界条件的处理。
#include <stdio.h> #include <stdlib.h> #define MAXSIZE 100 #define OK 1 #define ERROR 0 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; int length; } SqList; // 初始化顺序表 int InitList(SqList *L) { L->length = 0; return OK; } // 在位置 i 插入元素 e,i 从 1 开始计数 int ListInsert(SqList *L, int i, ElemType e) { if (i < 1 || i > L->length + 1) return ERROR; // 位置非法 if (L->length >= MAXSIZE) return ERROR; // 表满 for (int k = L->length; k >= i; k--) { L->data[k] = L->data[k - 1]; // 元素后移 } L->data[i - 1] = e; L->length++; return OK; } // 删除位置 i 的元素,用 e 带回 int ListDelete(SqList *L, int i, ElemType *e) { if (i < 1 || i > L->length) return ERROR; *e = L->data[i - 1]; for (int k = i; k < L->length; k++) { L->data[k - 1] = L->data[k]; // 元素前移 } L->length--; return OK; } // 按位查找 int GetElem(SqList L, int i, ElemType *e) { if (i < 1 || i > L.length) return ERROR; *e = L.data[i - 1]; return OK; }这段代码里最容易翻车的是插入位置 i 的取值范围:i 可以等于length + 1,表示插在表尾,但不能小于 1 或大于length + 1。删除时 i 最大只能到length,因为不能删一个不存在的元素。移动元素的循环方向也要注意,插入是倒着搬,删除是顺着搬,写反了数据就被覆盖。参数MAXSIZE设成 100 是实验常用值,实际工程里会用动态扩容,但实验阶段固定大小足够。
2.3 单链表基本运算与头插尾插的坑
单链表的结构和操作是实验的另一个重头。下面给出带头结点的单链表实现,头结点不存数据,只是为了统一插入删除逻辑。
typedef struct LNode { ElemType data; struct LNode *next; } LNode, *LinkList; // 头插法建立链表,输入 n 个元素 void CreateListHead(LinkList *L, int n) { *L = (LinkList)malloc(sizeof(LNode)); (*L)->next = NULL; for (int i = 0; i < n; i++) { LNode *p = (LNode *)malloc(sizeof(LNode)); scanf("%d", &p->data); p->next = (*L)->next; (*L)->next = p; } } // 尾插法建立链表,保持输入顺序 void CreateListTail(LinkList *L, int n) { *L = (LinkList)malloc(sizeof(LNode)); (*L)->next = NULL; LNode *r = *L; // r 始终指向尾结点 for (int i = 0; i < n; i++) { LNode *p = (LNode *)malloc(sizeof(LNode)); scanf("%d", &p->data); r->next = p; r = p; } r->next = NULL; } // 在位置 i 插入元素 e int ListInsert_L(LinkList L, int i, ElemType e) { LNode *p = L; int j = 0; while (p && j < i - 1) { // 找到第 i-1 个结点 p = p->next; j++; } if (!p || j > i - 1) return ERROR; LNode *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = p->next; p->next = s; return OK; }头插法建出来的链表顺序和输入相反,尾插法才和输入一致。很多同学实验报告里写“建立链表”结果输出是倒的,就是头插尾插没分清。插入操作里while循环的条件j < i - 1是为了让 p 停在待插位置的前一个结点,因为单链表插入必须知道前驱。如果 i 等于 1,循环一次都不走,p 就是头结点,直接插在头结点后面,逻辑正确。malloc之后一定要检查是否成功,实验环境虽然很少失败,但写习惯了没坏处。
3. 一元多项式加法:从顺序表到单链表的完整落地
3.1 多项式的数据结构设计
多项式每一项由系数和指数组成,比如3x^5 + 2x^2 + 1。用线性表存储时,每个节点存coef和expn两个字段。顺序表方案可以开两个数组分别存系数和指数,或者用一个结构体数组;单链表方案每个节点就是coef、expn、next。实验里通常要求按指数递增或递减有序存储,这样加法可以归并,不用双重循环。我一般让学生按指数递增存,头结点后面第一个节点指数最小,加法时两个链表从头扫,谁指数小谁先进结果,指数相等就系数相加,和为零则删掉该项。
3.2 单链表多项式加法的核心代码
下面这段代码实现两个按指数递增排列的多项式链表相加,结果存到新链表,不破坏原链表。
typedef struct PNode { float coef; // 系数 int expn; // 指数 struct PNode *next; } PNode, *Polynomial; // 创建一个多项式节点 PNode *NewPNode(float coef, int expn) { PNode *p = (PNode *)malloc(sizeof(PNode)); p->coef = coef; p->expn = expn; p->next = NULL; return p; } // 多项式加法:Pa = Pa + Pb,结果存入新链表 Pc void AddPolyn(Polynomial Pa, Polynomial Pb, Polynomial *Pc) { PNode *ha = Pa->next; // 跳过头结点 PNode *hb = Pb->next; *Pc = (PNode *)malloc(sizeof(PNode)); // 结果头结点 (*Pc)->next = NULL; PNode *r = *Pc; // 结果尾指针 while (ha && hb) { if (ha->expn < hb->expn) { r->next = NewPNode(ha->coef, ha->expn); ha = ha->next; } else if (ha->expn > hb->expn) { r->next = NewPNode(hb->coef, hb->expn); hb = hb->next; } else { float sum = ha->coef + hb->coef; if (sum != 0) { // 系数和为 0 不插入 r->next = NewPNode(sum, ha->expn); r = r->next; } ha = ha->next; hb = hb->next; continue; // 跳过下面的 r 移动,因为可能没插入 } r = r->next; } // 把剩余部分接上 while (ha) { r->next = NewPNode(ha->coef, ha->expn); r = r->next; ha = ha->next; } while (hb) { r->next = NewPNode(hb->coef, hb->expn); r = r->next; hb = hb->next; } }这段代码的关键在指数相等时的处理:系数相加后如果为 0,这一项就不应该出现在结果里,所以用continue跳过r = r->next,避免尾指针指向一个没插入的节点。另外结果链表用新节点分配,不直接复用原链表节点,这样原多项式不会被破坏,方便多次运算。参数Pa和Pb都是带头结点的链表,传入前必须保证按指数递增有序,否则归并逻辑不成立。如果实验要求原地相加,可以把ha和hb的节点直接摘下来接到结果链表上,省内存但会破坏原链表,看题目要求选。
3.3 多项式减法与乘法的扩展思路
减法本质就是加一个系数全部取反的多项式,你可以写一个NegatePolyn把 Pb 每个节点系数乘 -1,再调用AddPolyn。乘法稍微麻烦一点,需要双重循环:对 Pa 的每一项,遍历 Pb 的每一项,系数相乘、指数相加,生成一个新节点,然后把这些节点按指数有序插入结果链表。插入时可以用一个InsertTerm函数,每次从结果链表头开始找位置,指数相等就合并系数。实验里乘法通常只要求实现,不要求复杂度优化,O(n*m) 的暴力法完全够用。如果想让代码更干净,可以先按指数排序再合并同类项,但实验阶段没必要过度设计。
4. 避坑与排查:实验一最容易翻车的五个地方
4.1 顺序表插入位置判断写反
现象:调用ListInsert(L, 1, x)报错,或者插到表尾时数组越界。原因:判断条件写成i > L->length而不是i > L->length + 1,导致插在表尾被拒绝;或者忘了检查L->length >= MAXSIZE,表满还继续写。解决:插入合法位置是 1 到length + 1,删除合法位置是 1 到length,这两个范围背下来。
4.2 单链表遍历时丢了头指针
现象:链表建立后打印为空,或者第二次遍历时程序崩溃。原因:遍历时直接写while (L)然后L = L->next,把头结点指针弄丢了,后面再想操作链表就找不到头。解决:遍历用临时指针LNode *p = L->next;,头指针L始终不动。这是血泪经验,指针一丢,整个链表就成黑匣子了。
4.3 多项式加法结果出现重复指数项
现象:3x^2 + 2x^2结果里出现两个x^2项,而不是合并成5x^2。原因:归并时只比较了指数大小,没有处理指数相等的情况,或者相等时忘了移动两个链表的指针。解决:指数相等时系数相加,两个指针同时后移,和为零则不插入结果。检查while循环里三个分支是否都正确移动了ha和hb。
4.4 malloc 后没初始化 next 指针
现象:新建的节点接到链表尾部后,打印时多出乱码或死循环。原因:malloc只分配内存不初始化,p->next是随机值,如果没显式置NULL,尾节点后面会跟一段未知内存。解决:每次malloc后立刻设置p->next = NULL,尤其是尾插法和多项式乘法生成新节点时。
4.5 浮点数系数比较用 ==
现象:多项式加法判断系数和是否为零时,sum == 0有时不成立,导致本该删除的零项留在结果里。原因:浮点数运算有精度误差,0.1 + 0.2 != 0.3。解决:用fabs(sum) < 1e-6判断是否为零,实验里系数一般是整数或简单小数,但养成习惯没坏处。
5. 实验报告怎么写才不被扣分:验证方法与进阶技巧
实验报告不是代码堆砌,老师想看的是你理解了多少。我一般建议按这个顺序组织:先画顺序表和单链表的结构示意图,标清楚data、length、next这些字段;然后贴核心函数代码,每个函数下面用两三句话说明时间复杂度和边界条件;接着给测试用例,比如顺序表插入删除各测一次,多项式加法用(3x^2 + 2x + 1) + (2x^2 + 3)这种能覆盖指数相等、不等、剩余项三种情况的例子;最后写对比分析,顺序表和单链表在多项式加法里各有什么优劣。
验证多项式加法是否正确,可以手工算一遍预期结果,再和程序输出逐项比对。比如(3x^2 + 2x + 1) + (2x^2 + 3)预期是5x^2 + 2x + 4,程序输出必须一模一样,包括指数顺序。如果结果对但顺序不对,检查归并时是不是按指数递增走的。如果结果少项,检查剩余链表有没有接上。
进阶技巧方面,如果你想让实验代码更“工程化”,可以加一个PrintPolyn函数,按coef x^expn格式输出,系数为 1 或 -1 时省略数字,指数为 0 时只输出系数,指数为 1 时输出x不带^1。这个函数不复杂,但能让你的输出看起来专业很多。另外,多项式乘法可以用一个InsertTerm函数把新项有序插入结果链表,避免最后再排序,代码量差不多但效率更高。
我自己带实验的时候,发现学生最容易忽略的是“有序”这个前提。多项式链表如果不按指数有序,加法就得双重循环,复杂度从 O(n+m) 退化到 O(n*m),实验报告里分析复杂度时就会露馅。所以建立多项式时一定要用尾插法并按指数递增输入,或者建完后写个排序函数。这个习惯养成了,后面做更复杂的数据结构实验会轻松很多。希望帮到你。
本文还有配套的精品资源,点击获取