这一篇博客总结一下单链表常见方法的实现。相比于动态数组,对于单链表来说,它存储的元素并不要求在内存上连续,元素与元素之间通过指针串联起来。这样有许多好处,比如在单链表中间位置插入和删除元素的效率非常高,同时,它也有不足之处,它无法像动态数组那样可以通过下标直接访问。
链表实现的关键在于指针。一个节点除了保存着数据本身,还保存着指向下一个节点的指针。这样我们就可以通过一个节点找到下一个节点。从而将所有数据连接起来。
如上图所示,首先有一个头结点,该节点的数据域并不存放有效数据,指针域指向下一个节点。而最后的一个节点的指针域指向NULL。下面我来总结一下单链表的常见操作。
面向过程实现单链表操作
定义结构体
1 | typedef struct Node{ |
结构体中存储着int类型的数据,和指向自身类型的指针。
初始化链表
1 | NodePtr createList(void){ |
这里分配头节点的内存空间,并且将头结点中的指针赋值为NULL,防止其指向了不明的变量。由于头结点不存放实际的数据,所以并没有对data赋值。最后返回指向头结点的指针(即头指针)。
计算长度
1 | int listSize(NodePtr phead){ |
这里计算的是链表中有效节点的个数,由于头结点并不包含有效数据,所以直接跳过了头节点(phead=phead->next),while循环的条件为phead!=NULL,这是因为我们在设计链表的时候总是会将最后一个节点中的指针指向NULL,所以当phead==NULL时,链表也就循环完了。
打印链表
1 | void printList(NodePtr phead){ |
这里与上方求链表的长度是一致的。
增加数据
1 | void addNode(NodePtr phead){ |
这里以循环的方式增加链表的节点个数。通过收集控制台的输入来决定增加的数据的个数和具体的数据。每一个节点的增加都要经历如下的步骤:
- 分配内存空间
- 赋值,数据域赋对应值,指针域赋值为NULL
- 链接,将新节点链接到链表的尾端
下面的是一个节点加入到链表尾端的示意图:
在头部插入节点
1 | void insertAtHead(NodePtr phead){ |
尾部插入
1 | void insertAtTail(NodePtr phead){ |
匹配并删除第一个匹配节点
1 | void deleteOneNode(NodePtr phead,int _data){ |
这里我们需要一前一后的指针来分别指向一前一后两个节点。前面的指针负责寻找目标节点,当找到了目标节点之后就将目标节点后一个节点的指针域指向目标节点的下一个节点,这样目标节点也就不再属于该链表了。同时释放目标节点的内存。跳出循环,这样也就删除了匹配的第一个节点。
匹配并删除所有匹配节点
1 | void deleteAllNode(NodePtr phead,int _data){ |
这里使用递归的方式释放了整个链表。
测试
1 | int main(void){ |
输出结果
1 | 输入你要增加的节点数: |
面向对象实现单链表操作
我同样的用面向对象的思想实现了一遍这样的操作.
LinkedList.h
1 | /** |
LinkedList.cpp
1 | /** |
main.cpp
1 |
|
makefile
1 | main.exe:LinkedList.o main.o |