集美大学课程实验报告-实验2:线性表
| 项目名称 | 内容 |
|---|---|
| 课程名称 | 数据结构 |
| 班级 | 网安2411 |
| 指导教师 | 郑如滨 |
| 学生姓名 | 马梦佳 |
| 学号 | 202321514092 |
| 实验项目名称 | 实验2:线性表 |
| 上机实践日期 | 2025年3月6日 |
| 上机实践时间 | 2学时 |
一、目的(本次实验所涉及并要求掌握的知识点)
- 熟练掌握线性表的存储结构——顺序表和链表。
- 掌握线性表区间删除、插入、逆置等基本操作的实现。
- 熟练运用 Visual Studio 的调试功能。
二、实验内容与设计思想
题目1:区间删除
主要内容:实现一个函数,删除顺序表中指定区间内的元素。
函数相关伪代码
函数 main
输出 "请输入线性表元素个数:"
输入 n
调用 CreateList(L, n) 创建顺序表
输出 "请输入要删除的区间值min, max:"
输入 min 和 max
调用 DelNode(L, min, max) 删除区间元素
调用 DispList(L) 输出顺序表
调用 DestroyList(L) 销毁顺序表
函数 CreateList(顺序表 L, 整数 n)
创建一个新的顺序表 L
循环 对于 i 从 0 到 n-1
输出 "请输入第 i+1 个数据"
输入 L->data[i]
L->length = n
函数 DelNode(顺序表 L, 整数 min, 整数 max)
整数 newIndex = 0
循环 对于 i 从 0 到 L->length - 1
如果 L->data[i] 小于 min 或 L->data[i] 大于 max
L->data[newIndex] = L->data[i]
newIndex = newIndex + 1
L->length = newIndex
函数 DispList(顺序表 L)
如果 L->length 等于 0
输出 "线性表为空!"
否则
循环 对于 i 从 0 到 L->length - 1
输出 L->data[i]
如果 i 小于 L->length - 1
输出 " "
函数 DestroyList(顺序表 L)
释放顺序表 L 的内存
函数代码
#include <iostream>
#define MaxSize 100000
using namespace std;
typedef int ElemType;
typedef struct
{
ElemType data[MaxSize]; //存放顺序表元素
int length; //存放顺序表的长度
} List;
typedef List* SqList;
void CreateList(SqList& L, int n); //创建顺序表
void DelNode(SqList& L, int min, int max); //删除区间元,min,max表示删除的区间
void DispList(SqList L); //输出顺序表内容
void DestroyList(SqList& L); //销毁顺序表
int main()
{
int i, n;
int min, max;
SqList L;
cout << "请输入线性表元素个数:";
cin >> n;
CreateList(L, n); //创建线性表,输入数据保证是有序的
cout << "请输入要删除的区间值min,max:";
cin >> min >> max;
DelNode(L, min, max);
DispList(L);
DestroyList(L);
}
void CreateList(SqList& L, int n)
{
L = new List;
for (int i = 0; i < n; i++)
{
cout << "请输入第"<<i+1<<"个数据:";
cin >> L->data[i];
}
L->length = n;
}
void DelNode(SqList& L, int min, int max)
{
int newIndex = 0;
for (int i = 0; i < L->length; i++)
{
if (L->data[i]<min || L->data[i]>max)
{
L->data[newIndex++] = L->data[i];
}
}
L->length = newIndex;
}
void DispList(SqList L)
{
if (L->length == 0)
{
cout << "线性表为空!";
}
for (size_t i = 0; i < L->length; i++)
{
cout << L->data[i];
if (i < L->length - 1)
{
cout << " ";
}
}
}
void DestroyList(SqList& L)
{
delete L;
}
时间复杂度与空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
题目2:头插法或尾插法创建链表
主要内容:实现链表的创建,分别使用头插法和尾插法创建链表。
函数相关伪代码
函数 main
输出 "请输入要插入的链表节点个数:"
输入 n
调用 CreateListF(L, n) 头插法创建链表
调用 DispList(L) 输出链表
调用 DestroyList(L) 销毁链表
函数 DestroyList(链表 L)
初始化指针 p = L
循环 当 L 不为空
将 p 指向 L
L = L->next
释放 p
函数 CreateListF(链表 L, 整数 n)
创建头结点 L
L->next = NULL
循环 对于 i 从 0 到 n-1
创建新节点 newNode
输出 "请输入第 i+1 个元素"
输入 newNode->data
newNode->next = L->next
L->next = newNode
函数 DispList(链表 L)
如果 L->next 为 NULL
输出 "空链表"
否则
初始化指针 p = L->next
输出 p->data
循环 对于 p->next 不为 NULL
输出 " " 和 p->data
p = p->next
输出换行
函数代码
#include <iostream>
using namespace std;
typedef int ElemType;
typedef struct LNode //定义单链表结点类型
{
ElemType data;
struct LNode* next; //指向后继结点
} LNode, * LinkList;
void CreateListF(LinkList& L, int n); //头插法建链表
void DispList(LinkList L); //输出链表
void DestroyList(LinkList& L); //销毁链表
int main()
{
LinkList L;
int n;
cout << "请输入要插入的链表节点个数:";
cin >> n; //输入链表节点个数
CreateListF(L, n); //头插法建链表
DispList(L); //输出链表
DestroyList(L); //销毁链表
return 0;
}
void DestroyList(LinkList& L)
{
LinkList p = L;
while (L)
{
p = L;
L = L->next;
delete p;
}
}
void CreateListF(LinkList& L, int n)//头插法建链表,L表示带头结点链表,n表示数据元素个数
{
L = new LNode;
L->next = nullptr;
for (int i = 0; i < n; i++)
{
LNode* newNode = new LNode;
cout << "请输入第" << i+1 << "个元素:";
cin >> newNode->data;
newNode->next = L->next;
L->next = newNode;
}
}
void DispList(LinkList L)
{
LinkList p = L->next;
if (p == NULL)
{
cout << "空链表!" << endl;
}
else
{
cout << p->data;
p = p->next;
while (p != NULL)
{
cout << " " << p->data;
p = p->next;
}
cout << endl;
}
}
时间复杂度与空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(n)
题目3:单链表的逆置
主要内容:实现单链表的逆置。
函数相关伪代码
函数 main
输出 "请输入要插入的链表节点个数:"
输入 n
调用 CreateListR(L, n) 尾插法创建链表
调用 ReverseList(L) 逆转链表
调用 DispList(L) 输出链表
调用 DestroyList(L) 销毁链表
函数 ReverseList(链表 L)
如果 L->next 为 NULL 或 L->next->next 为 NULL
返回
初始化指针 pre = NULL, cur = L->next, next = NULL
当 cur 不为空
更新 next = cur->next // 保存当前节点的下一个节点
更新 cur->next = pre // 当前节点指向前一个节点
更新 pre = cur // 前一个节点后移
更新 cur = next // 当前节点后移
更新 L->next = pre // 头结点指向新的头节点
函数代码
#include <iostream>
using namespace std;
typedef int ElemType;
typedef struct LNode //定义单链表结点类型
{
ElemType data;
struct LNode* next; //指向后继结点
} LNode, * LinkList;
void CreateListR(LinkList& L, int n); //尾插法建链表
void DispList(LinkList L); //输出链表
void DestroyList(LinkList& L); //销毁链表
void ReverseList(LinkList& L); //逆转链表
int main()
{
LinkList L;
int n;
cout << "请输入要插入的链表节点个数:";
cin >> n; //输入链表节点个数
CreateListR(L, n); //尾插法建带头结点链表
ReverseList(L); //逆转链表
DispList(L); //输出链表
DestroyList(L); //销毁链尾
return 0;
}
void DestroyList(LinkList& L)
{
LinkList p = L;
while (L)
{
p = L;
L = L->next;
delete p;
}
}
void CreateListR(LinkList& L, int n)//尾插法建链表,L表示带头结点链表,n表示数据元素个数
{
L = new LNode;
L->next = nullptr;
LNode* p = L;
for (int i = 0; i < n; i++)
{
LNode* newNode = new LNode;
cout << "请输入第" << i + 1 << "个元素:";
cin >> newNode->data;
newNode->next = nullptr;
p->next = newNode;
p = newNode;
}
}
void DispList(LinkList L)
{
LinkList p = L->next;
if (p == NULL)
{
cout << "空链表!" << endl;
}
else
{
cout << p->data;
p = p->next;
while (p != NULL)
{
cout << " " << p->data;
p = p->next;
}
cout << endl;
}
}
void ReverseList(LinkList& L) //逆转链表
{
if (L->next == nullptr || L->next->next == nullptr) {
return;
}
LinkList pre = nullptr;
LinkList cur = L->next;
LinkList next = nullptr;
while (cur != nullptr)
{
next = cur->next; // 保存当前节点的下一个节点
cur->next = pre; // 当前节点指向前一个节点
pre = cur; // 前一个节点后移
cur = next; // 当前节点后移
}
L->next = pre; // 头结点指向新的头节点
}
时间复杂度与空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
三、实验使用环境(本次实验所使用的平台和相关软件)
- 操作系统:Windows 11
- 编程语言:C++
- 开发工具:Visual Studio 2022
- 编译器:C/C++ 17.13
四、实验步骤和调试过程(实验步骤、测试数据设计、测试结果分析)
题目1:区间删除
本机运行截图

PTA提交截图

题目2:头插法或尾插法创建链表
本机运行截图


PTA提交截图


题目3:单链表的逆置
本机运行截图

PTA提交截图

五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)
遇到的问题及解决方法:
- 问题:在实验中,特别是在实现链表的逆转和尾插法时,指针操作是一个难点。一开始我不太理解如何通过指针的移动来实现节点的插入和删除,尤其是链表的逆转,感觉指针的前后关系比较复杂,容易混淆。
- 解决方法:通过阅读教材和网络上的资料,逐步理解了指针的指向和移动方式。在逆转链表时,理解了“当前节点的 next 指向前一个节点”的核心思想。通过画图帮助自己理解指针的变化,使得链表操作变得更加清晰。同时,借助Visual Studio的调试功能,可以单步执行代码,查看指针的实时变化,进一步加深了对链表操作的理解。
- 问题:在实现链表的逆置时,我一开始未能正确处理头节点的指向,导致逆置操作后链表的头节点没有被正确更新,从而出现链表无法正确输出的问题。
- 解决方法:通过调试和仔细分析代码,我意识到在逆置链表时,需要将头结点的 next 指向新的头结点(即原链表的最后一个节点)。我修改了 ReverseList 函数中的代码,确保了头结点指向逆置后的链表头,并且在链表逆置完成后正确更新了头节点的指针。
实验体会和收获:
- 通过这次实验,我深入理解了链表和顺序表的区别。顺序表使用数组存储数据,具有良好的随机访问性,但是在插入和删除操作时效率较低,尤其是在中间位置进行插入或删除时,需要移动大量元素。而链表则通过指针连接元素,插入和删除操作较为高效,但缺少随机访问性,查找元素时必须按顺序遍历。
- 在实验过程中,我学会了使用 Visual Studio 的调试功能,通过单步执行代码、查看变量的值来更清晰地理解程序的执行流程。这种调试能力不仅帮助我更好地理解链表操作,也提高了我排查问题的效率。
浙公网安备 33010602011771号