集美大学课程实验报告-实验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提交截图
PTA提交截图

题目2:头插法或尾插法创建链表

本机运行截图
本机截图
本机截图

PTA提交截图
PTA提交截图
PTA提交截图

题目3:单链表的逆置

本机运行截图
本机截图

PTA提交截图
PTA提交截图


五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)

遇到的问题及解决方法:

  1. 问题:在实验中,特别是在实现链表的逆转和尾插法时,指针操作是一个难点。一开始我不太理解如何通过指针的移动来实现节点的插入和删除,尤其是链表的逆转,感觉指针的前后关系比较复杂,容易混淆。
    • 解决方法:通过阅读教材和网络上的资料,逐步理解了指针的指向和移动方式。在逆转链表时,理解了“当前节点的 next 指向前一个节点”的核心思想。通过画图帮助自己理解指针的变化,使得链表操作变得更加清晰。同时,借助Visual Studio的调试功能,可以单步执行代码,查看指针的实时变化,进一步加深了对链表操作的理解。
  2. 问题:在实现链表的逆置时,我一开始未能正确处理头节点的指向,导致逆置操作后链表的头节点没有被正确更新,从而出现链表无法正确输出的问题。
    • 解决方法:通过调试和仔细分析代码,我意识到在逆置链表时,需要将头结点的 next 指向新的头结点(即原链表的最后一个节点)。我修改了 ReverseList 函数中的代码,确保了头结点指向逆置后的链表头,并且在链表逆置完成后正确更新了头节点的指针。

实验体会和收获:

  • 通过这次实验,我深入理解了链表和顺序表的区别。顺序表使用数组存储数据,具有良好的随机访问性,但是在插入和删除操作时效率较低,尤其是在中间位置进行插入或删除时,需要移动大量元素。而链表则通过指针连接元素,插入和删除操作较为高效,但缺少随机访问性,查找元素时必须按顺序遍历。
  • 在实验过程中,我学会了使用 Visual Studio 的调试功能,通过单步执行代码、查看变量的值来更清晰地理解程序的执行流程。这种调试能力不仅帮助我更好地理解链表操作,也提高了我排查问题的效率。

六、附件(参考文献和相关资料

  1. C++菜鸟教程
posted on 2025-03-09 15:19  醉梦4781  阅读(69)  评论(0)    收藏  举报