今天我们将开始第二个数据类型-链表的学习,同样我们还是用最原始的方式,自己申请内存管理内存来实现一个链表。
01、定义
什么是链表?链表在物理存储结构上表现为非顺序性和非连续性,因此链表的数据元素物理存储位置是随机的,动态分配的;而在逻辑结构上表现为线性结构的特点,即元素一个连着一个元素串起来像一条线。
节点:其中链表元素又叫节点,一个节点主要包含数据域和指针域,其中数据域主要存放数据元素,而指针域主要存放下一个节点存储位置地址。
头指针:一个表示链表第一个节点位置的普通指针,并且永远指向第一个节点位置,方便后面使用链表。
头节点:通常表示链表的第一个节点,并且节点内数据域为空,因此也叫空节点,其作用主要用于解决一些特殊问题,因此也可以省略。
首元节点:由于头节点数据域为空,因此链表的第一个数据域不为空的节点叫首元节点,只是一个名称,并没有什么实际意义。
02、分类
链表有两种分类方法,其一可以分为静态链表和动态链表,其二可以分为单向链表、双向链表以及循环链表。
单链表只有一个方向,每个节点包含数据域和指向下一个节点的指针域。
双向链表有两个方向,即每个节点包含数据域以及同时指向上一个节点和下一个节点的指针域。
循环链表指链表首尾相连,即最后一个节点的指针域指向第一个节点。循环链表也分单向循环链表和双向循环链表,原理都一样。
03、实现
下面我们一起使用最原始的方式,自己申请内存空间,自己维护,完成链表的实现。
1、ADT定义
我们首先来定义链表的ADT(单链表)。
ADT LinkedList{
数据对象:D 是一个非空的元素集合,D = {a1, a2, ..., an},其中 ai 表示一个元素即节点,一个节点存储着数据和指向下一个节点的指针。
数据关系:D中的节点通过指针进行连接,每一个节点都包含一个指向下一个节点的指针。
基本操作:[
Init(n) :初始化一个空链表,即声明一个头指针,如有必要也可以声明一个头节点。
Length:返回链表长度。
HeadNode:返回头节点。
Find(v):返回数据域v对应的节点。
Update(n,v):更新n节点的数据域。
InsertAfter(n,v):在n节点后面添加数据域为v的新节点。
Remove(n):移除n节点。
Destroy():销毁链表。
]
}
定义好链表ADT,下面我们就可以开始自己实现一个数据域为string类型的链表。
2、定义类
首先我们需要定义节点,其中包含两个字段一个是存放数据、一个是存放指针,代码如下。
然后再定义链表实现类MyselfLinkedList,用来实现链表的相关操作。
因为我们直接管理内存,所以需要一个维护内存的指针字段;
因为我们直接获取链表长度,所以需要一个存储链表长度字段;
因此我们的MyselfLinkedList类初步是这样的:
3、初始化Init
初始化结构主要做几件事。
a.分配内存空间;
b.什么头指针;
c.创建头节点;
d.维护链表长度属性;
具体实现代码如下:
4、获取链表长度 Length
这个比较简单直接把链表长度私有字段返回即可。
5、获取头节点 HeadNode
获取头节点主要是为了方便数据处理,可以通过头指针直接读取内存地址获取。具体代码如下:
同样我们也可以定义一个尾节点属性,可以方便使用,原理都差不多,这里就不赘述了。
6、在指定节点后插入节点 InsertAfter
通过前面对链表结构的了解,要想再两个节点之间加入一个新节点,只需要把两者之间的线剪断,即前一个节点的指针域需要重新指向新节点,并且新节点的指针域要指向后一个节点,其他保持不变,如下图:
业务逻辑清楚了,我们再来梳理代码逻辑,要想实现这个功能我们大致需要一下几步:
a.获取指定节点的指针;
b.创建一个新的节点;
c.重新调整指定节点及新节点指针域;
d.把指定节点和新节点指针调整后数据更新到内存中;
e.更新链表长度属性;
具体实现如下:
这里只实现了一个在指定节点后插入节点,我们还可以实现在指定节点前插入,在首元节点前插入,在尾节点后添加,都是可以的,感兴趣的可以自己实现试试。
7、根据数据域查找节点 Find
在链表中对查找是不友好的,因为查找一个值,需要从链表头一个一个往后查找,实现逻辑到不复杂,具体实现代码如下:
8、更新指定节点数据域 Update
这个方法逻辑也比较简单,只需要找到节点指针,然后把节点更新,最后把更新后的数据写入内存即可。
9、移除指定节点 Remove
如果要想移除一个节点,则需要把指定节点与前后节点的连接删除,然后把前后两个节点建立起连接,同时需要手动释放被删除节点内存。如下图。
具体代码实现如下:
10、销毁链表 Destroy
销毁链表主要是使用因为是我们自己手动管理内存,用完后要及时清理,放在内存泄漏等意外情况出现。代码也很简单,循环把每个节点内存释放即可,如下代码:
11、释放内存 Dispose
因为我们实现了IDisposable接口,所有需要实现Dispose方法,只需要在Dispose方法中调用上面销毁链表Destroy方法即可。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
领取专属 10元无门槛券
私享最新 技术干货