数据结构系列学习(三) - 单链表(Linked_List)

目录

引言:

学习:

代码实现:

链表的结构体设计:

函数功能说明:

头文件(Linked_List.h)中的函数声明:

源文件(Linked_List.cpp) 中函数功能的具体实现:

1.初始化函数(Init_List):

插入函数组(Insert): 

问题:链表的插入需不需要判满?

2.头部插入函数(Insert_head):

3.尾部插入函数(Insert_tail):

4.按位置插入函数(Insert_pos):

删除函数组(Delete): 

5.头部删除函数(Delete_head):

6.尾部删除函数(Delete_tail):

7.按位置删除函数(Delete_pos):

8.按值删除函数(Delete_val):

函数中循环的不同:

9.查找函数(Search):

10.判空函数(IsEmpty):

11.清空函数(Clear):

12.销毁函数(Destroy):

13.打印函数(Show):

14.获取长度函数(Get_length):

测试: 

测试插入函数组:

测试初始化函数、按位置插函数:

测试头插函数:

测试尾插函数:

测试删除函数组:

测试头删函数 :

测试尾删函数: 

测试按位置删函数: 

测试按值删函数:

测试清空函数:

 测试销毁函数:

测试获取长度函数: 

测试查找函数:

总结: 

参考资料:


引言:

在之前我们系统的学习了数据结构中基础的概念、时间复杂度,并且用代码实现了顺序表(Contiguous_List),在对顺序表的学习和实现的文章总结中,我们提到了顺序表的优势在于可以直接访问顺序表中任意一个元素,但是劣势在于如果再头部或者中间位置进行插入或者删除操作,移动元素所产生时空开销较大。在这篇文章中我们将要进行学习的链表(Linked_List)在计算机内存中不一定连续,也可以有效的降低插入和删除元素所产生的时空开销,但相比较顺序表有着优秀时空开销的链表同样也有它的缺点,我们将对链表进行系统的学习并使用代码对他进行实现。

数据结构学习目录:

数据结构系列学习(一) - An Introduction to Data Structure

数据结构系列学习(二) - 顺序表详解(Contiguous_List)

学习:

为了避免插入和删除所产生的巨大的时空开销,线性表有另外一种表现形式——链表。链表相较于顺序表不同的是链表并不是连续存储的。

链表是由一些列不必在内存中相连的结构组成的。每一个结构均含有表元素和指向包含该元素后继元素的结构的指针。我们也把这个指针叫做Next指针。最后一个单元的Next指针指向NULL,此处的NULL为0.


 

特点:逻辑相邻,但是物理上并不一定相邻
因为数据元素在物理上并不要求连续,则是虚了顺序表可以随机访问的优势
单链表:用一组任意的存储单元,存储线性表的数据元素(这组数据可以连续,也可以不连续。
所以为了表示每个节点An和其后继节点An+1之间逻辑关系,每一个节点不仅仅需要保存自己的有效值(数值域),还需要保存下一个节点的地址(指针域))
单链表一般分为两种实现方式:带头节点(常用)、不带头节点(实现起来相对比较麻烦)

代码实现:

注:这里我们实现的是不带头节点的单链表。

链表的结构体设计:

在学习链表的过程中,我们已经知道链表包含数据域(data)和指针域(next),我们在设计结构体的时候也应该与这两个东西保持一致,在这里我们先重新定义范型int类型为Elem_type,所以链表中的数据域自然也就是Elemtype类型了,链表中的指针域就是链表结构体类型的指针:

typedef int Elem_type;//给出范型重命名
typedef struct Node{
    Elem_type data;//数据域(保存数据的有效值)
    struct Node* next;//指针域(保存有效节点,也就是node的节点)
}Node,*PNode;//将结构体重命名为Node,结构体类型的指针变量命名为PNode

函数功能说明:

实现链表首先要明确的就是我们即将要实现的链表能做什么,也就是链表应该具有哪些功能,下面就是链表应该具有的功能:

初始化函数(Init_List)

头部插入函数(Insert_head)

尾部插入函数(Insert_tail)

按位置插入函数(Insert_pos)

头部删除函数(Delete_head)

尾部删除函数(Delete_tail)

按位置删除函数(Delete_pos)

按值删除函数(Delete_val)

查找函数(Search)

判空函数(IsEmpty)

清空函数(Clear)

销毁函数(Destroy)

打印函数(Show)

获取有效个数(长度)函数(Get_length)

头文件(Linked_List.h)中的函数声明:

//1 初始化
void Init_List(struct Node* plist);
//2 头插
bool Insert_head(struct Node* plist,Elem_type val);
//3 尾插
bool Insert_tail(struct Node* plist,Elem_type val);
//4 按位置插
bool Insert_pos(struct Node* plist,int pos,Elem_type val);
//5 头删
bool Delete_head(struct Node* plist);
//6 尾删
bool Delete_tail(struct Node* plist);
//7 按位置删
bool Delete_pos(struct Node* plist,int pos);
//8 按值删
bool Delete_val(struct Node* plist,Elem_type val);
//9 查找 返回的是查找到的这个节点的地址
struct Node* Search(struct Node* plist,Elem_type val);
//10 判空
bool IsEmpty(struct Node* plist);
//11 清空
bool Clear(struct Node* plist);
//12 销毁
bool destroy(struct Node* plist);
//13 打印
void Show(struct Node* plist);
//14 获取有效个数(长度)
int Get_length(struct Node* plist);

源文件(Linked_List.cpp) 中函数功能的具体实现:

1.初始化函数(Init_List):

我们要初始化链表首先要做的就是将plist的指针域置为NULL:

代码:

void Init_List(struct Node* plist)
{
    //1 判断plist是否为空地址(安全性处理)
    assert(plist != NULL);
    //2 对于plist指向的头节点里面的每一个成员变量进行赋值
    plist->next = nullptr;//将链表中的头节点的next赋值为空
}

插入函数组(Insert): 

在写插入函数组之前,我们首先应该知道对于链表中的插入操作(头部插入、尾部插入、按位置插入)到底是怎样完成的,这里我们给出一张图:

我们以上面画的链表结构图为例子,这是链表的初始状态、pnewnode代表的是待插入的新节点:

我们知道,在不带头节点的单链表中初始节点的数据域为空,且Next指针指向下一个节点的数据域,那么如果我要执行插入操作,我就操纵本来应该指向下一个节点数据域的Next指针这时指向pnewnode节点的数据域,然后再操纵pnewnode的next指针指向下一个节点的数据域,使其形成一个环形结构即可,如图:

这就是链表中插入的操作演示。 

问题:链表的插入需不需要判满?

在写头部插入函数之前,我们先来探讨一个问题,链表的插入需不需要判满?

在之前的顺序表的文章中曾经说过,顺序表是我们申请的一整块连续的内存,顺序表在存储结构和物理上都是连续的,因此在顺序表的插入和删除操作中,我们首先要做的就是判断顺序表是否已满,如果满了就进行扩容操作再进行添加或删除操作,如果没满就直接进行添加或删除操作,顺序表的添加或删除操作是将被移动元素前面或者后面的全部元素进行移动,时空开销较大。

相较于顺序表,链表本身是是线性表的另一种表现形式,链表在存储结构上连续,但在物理上是不连续的。链表中存在两个成员一个是存储数据的数据域另一个是指阵域,存储下一个节点的地址,这也就是在链表中物理空间不连续但是在存储结构上是连续的原因。链表中的节点都是一步步通过malloc函数申请而得来的,申请完节点之后,通过对新节点的数据域进行赋值,通过修改指针域的指向来达到添加新节点的目的,所以只要堆区还有足够的空间,链表就无需判满。

2.头部插入函数(Insert_head):

我们要插入数据,就必须要在链表新节点的数据域中存放该数据,而链表的新节点是需要通过malloc函数在堆区进行申请的,所以大致的过程就能表示为:对指针做安全性处理,在堆区申请pnewnode新节点的内存空间,申请完成之后将数据存放在新节点的数据域中,修改pnewnode的next域,然后再更新plist的头节点:

代码:  

bool Insert_head(struct Node* plist,Elem_type val)
{
    //1 安全性处理
    assert(plist != NU
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Marty_Zu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值