线性表是典型的线性结构,用链式存储的方式实现ADT List,并在此基础上实现链表的输入输出以及插入删除。
[实验目的]
- 使用自己编写头文件。
- 理解文件包含的意义。
- 掌握线性表的链式存储结构。
- 实现单链表的基本操作。
[实验内容及要求]
- 将预定义常量和类型封装成一个头文件“predef.h”,供实验使用。
- 参照课件(或者课本),实现链表的存储结构LNode和LinkList及其基本操作:构造空链表、获取某个元素的值、插入新元素、删除某个元素、清空单链表等,封装在头文件“linklist.h”中。
- 在源文件“linklist_try.cpp”中,调用基本操作实现功能函数——链表的批量输入:依次输入n个数据元素存放在链表中(n由用户在程序运行时输入)。
- 在源文件“linklist _try.cpp”中,调用基本操作实现功能函数——链表的批量输出:依次输出当前链表中的所有数据元素。
- 在源文件“linklist _try.cpp”中,在主函数中调用上述功能函数和基本操作:批量向链表中输入数据元素,尝试插入和删除某个数据元素,输出链表中的所有数据元素并观察每次操作的结果是否正确,程序结束前清空链表。
[测试数据](示例)
- 1 2 3 4 5 6 7 8 9 10
- 12 32 65 9 21
- 98 5 87 66 4 7 54
- 101 6 88 76 5 89
- 10 109 87 78 86 5 61
- ……
- ……
[程序流程图](主要功能模块)
[实验过程与结果](抓图和分析)
测试主函数:
测试输出:
[实验体会](随笔)
一、链表的遍历
在实验中,我对于链表的遍历有如下思考:
其实每次在需要遍历的地方都大同小异,均使用如下代码即可实现,同时保障了代码的健壮性,同时设定两个参量,保证在遍历到达指定位置前指针不为空,即链表未到达最后一个结点
LNode* p = L;
int j = 0;
while (j < pos && p) // 寻找第pos个位置
{
p = p->next;
j++;
}
if (!p || j > pos) return ERROR;
这样即可保证找到第pos个位置的结点,并进行其他操作。
二、链表的输出
在对链表进行遍历输出时,需要注意链表不能为空,同时注意新定义的指针指向结点不能为空,否则输出该指针指向数据时会越界访问!
// 实现对链表的输出
Status ListShow_L(LinkList L)
{
LNode* p = L;
if (p == NULL)
{
printf("链表为空!\n");
return OK;
}
p = p->next;
while (p != NULL)
{
std::cout << p->data << " "; // 注意!此处要先输出
p = p->next;
// 再让p指向下一位,否则p在最后一位会指向NULL,此时没有数据输出报错
}
std::cout << std::endl;
return OK;
} // ListShow_L
三、链表的清空操作
在对链表进行清空操作时,需要定义当前结点的指针与其后面结点的指针,前面指针是为了删除当前结点,后面指针是为了遍历整个链表,从头节点开始,向后一次遍历,特别注意:在最后要将头节点指向NULL,此时才能说明链表为空,因为在链表初始化函数中,将头指针传入后会新创建头节点,因此要注意把链表中全部结点情况并让头指针指向空才算是把整个链表清空。
// 清空单链表
Status ListEmpty_L(LinkList& L)
{
LNode* current = L->next;
LNode* next = current->next;
while (current != NULL)
{
delete current;
current = next;
if (current == NULL) break;
next = next->next;
}
L = NULL;
return OK;
} // ListEmpty_L
[思考]
1.结合实验1,思考一下使用已有的数据结构有什么优点?
在对整个线性表的两大存储结构(顺序表和链表)实现后,我认为使用已有的数据结构可以:
- 易于对整个代码进行阅读和维护,增加了对代码的可读性,让代码标准化。
- 可以让代码变得模块化,便于增加模块和修改某一模块内容
- 可以增加代码的正确性,在前人已经实验有效的基础上,能让代码更加有效正确
- 经过优化的数据结构(如哈希表)可以提供接近常数时间的插入、删除和查找操作。
- 链表易于扩展,可以很容易地增加新的元素或修改已有元素
2.在实现链表的存储结构及其基本操作的过程中,有什么困难和疑问?
我在实验过程中,对这些操作的想法已经写在了随笔中,在之后的实验中,应该多注意堆的创建和销毁,同时要注意指针指向空结点时不能再使用其结点内部的数据等等。
最后将全部代码放出,欢迎大家一起讨论思考!
linklist_try.cpp
#include <iostream>
#include "linklist.h"
/*
author: Electronic_Rain
date: 2024/09/28
*/
int main()
{
LinkList L; // 创建链表
ListInit_L(L); // 链表初始化
int n;
std::cin >> n; // 输入链表中元素个数
ListInMore_L(L, n); // 批量输入链表
ListShow_L(L); // 链表显示
ListInsert_L(L, 2, 3); // 链表在第2位置插入元素3
ListShow_L(L); // 链表显示
ElemType e = 0;
ListDelete_L(L, 1, e); // 链表在删除第一位置的元素
ListShow_L(L); // 链表显示
GetElem_L(L, 3, e); // 获取链表第3位元素
std::cout << e << std::endl;
ListEmpty_L(L); // 清空链表
ListShow_L(L); // 链表显示
return 0;
}
predef.h
#pragma once
// 函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define Status int
linklist.h
#pragma once
#include "predef.h"
#include <iostream>
#include <stdlib.h>
#define ElemType int // 设置数据类型为int型
//-----线性表的单链表存储结构-----//
typedef struct LNode{
ElemType data;
struct LNode* next;
}LNode, *LinkList;
// 链表初始化
Status ListInit_L(LinkList& L)
{
L = new LNode;
L->next = NULL;
if (L == NULL) return ERROR;
return TRUE;
} // ListInit_L
// 获取链表中某位置的值并返回给e
Status GetElem_L(LinkList& L, int pos, ElemType& e)
{
LNode* p = L;
int j = 0;
while (j < pos && p) // 寻找第pos个位置
{
p = p->next;
j++;
}
if (!p || j > pos) return ERROR;
e = p->data;
return OK;
} // GetElem_L
// 在第pos个位置插入元素
Status ListInsert_L(LinkList& L, int pos, ElemType e)
{
LNode* p = L;
int j = 0;
while (j < pos-1 && p) // 寻找第pos-1的位置
{
p = p->next;
j++;
}
if (!p || j > pos - 1) return ERROR;
LNode* px = new LNode;
px->data = e;
px->next = p->next;
p->next = px;
return OK;
} // ListInsert_L
// 删除第pos个元素,并用e返回其值
Status ListDelete_L(LinkList& L, int pos, ElemType e)
{
LNode* p = L;
int j = 0;
while (p && j < pos - 1) // 寻找第pos-1的位置
{
p = p->next;
j++;
}
if (!(p->next) || j > pos - 1) return ERROR;
LNode* del = p->next;
p->next = del->next;
delete del;
return OK;
} // ListDelete_L
// 清空单链表
Status ListEmpty_L(LinkList& L)
{
LNode* current = L->next;
LNode* next = current->next;
while (current != NULL)
{
delete current;
current = next;
if (current == NULL) break;
next = next->next;
}
L = NULL;
return OK;
} // ListEmpty_L
// 实现对链表的批量加入
Status ListInMore_L(LinkList& L, int n)
{
LNode* p = L;
int count = 0;
while (n--)
{
count++;
std::cout << "请输入第" << count << "个元素:";
ElemType e;
std::cin >> e;
p->next = new LNode;
p->next->data = e;
p = p->next;
p->next = NULL;
}
return OK;
} // ListInMore_L
// 实现对链表的输出
Status ListShow_L(LinkList L)
{
LNode* p = L;
if (p == NULL)
{
printf("链表为空!\n");
return OK;
}
p = p->next;
while (p != NULL)
{
std::cout << p->data << " "; // 注意!此处要先输出
p = p->next; // 再让p指向下一位,否则p在最后一位会指向NULL,此时没有数据输出报错
}
std::cout << std::endl;
return OK;
} // ListShow_L