一、单链表的基本概念
-
链表不同于顺序表,顺序表底层采用数组作为存储容器,需要分配一块连续且完整的内存空间进行使用,而链表则不需要,它通过一个指针来连接各个分散的结点,形成了一个链状的结构,每个结点存放一个元素,以及一个指向下一个结点的指针,通过这样一个一个相连,如果不存在下一个节点就指向空,直到最后形成了链表。它不需要申请连续的空间,只需要按照顺序连接即可,虽然物理上可能不相邻,但是在逻辑上依然是每个元素相邻存放的,这样的结构叫做链表(单链表)。
-
链表可分为带头节点的链表,和不带头节点的链表
- 带头结点的链表第一个节点不存放元素,且指向链表的首元节点(如果链表不为空的话),首元节点就是链表中存有有效数值的第一个节点
- 不带头节点的链表,首元节点就代替了头节点的位置
接下来我向铁汁们介绍的是带头节点的单链表
- 当前节点的前一个节点叫做该节点的"前趋",后一个节点叫做"后继"
二、单链表的存储结构
依赖结构体来实现链表的存储结构
用指针来实现指向下一个节点的操
typedef int E;
struct ListNode{
E element;//节点所存储的元素
struct ListNode * next;//指向下一个节点的地址,建立联系,实现逻辑意义上的相邻
};
三、对单链表的操作
- 初始化单链表
void initList(Node node){
node->next = NULL;//因为链表为空,所以头节点指向空
}
- 对单链表进行插入
想要插入必须先找到当前节点的前驱节点,不然链表会断
先遍历链表找到指定序号节点的前趋节点(因为有头节点,所以最index == 0时 当前节点为目标节点的前驱节点)
找到后先创建节点,并对其进行赋值
让新创建的节点指向前驱节点的后继
再让前驱节点指向新创建的节点
从而便完成了插入的操作
_Bool insertList(Node current,E element,int index){
if(index < 1) return 0;
while(--index){//先遍历链表找到指定序号节点的前趋节点
current = current->next;
if(current == NULL){//还没遍历到就指向空了说明该序号不存在
return 0;
}
}
Node node = malloc(sizeof(struct ListNode));
if(node == NULL) return 0;//没创建成功就返回false
node->element = element;
node->next = current->next;
current->next = node;
return 1;
}
我们来测试一下
int main() {
struct ListNode head;
initList(&head);
for (int i = 0; i < 3; ++i) {
insertList(&head, (i+1) * 100, 1); //依次插入3个元素
}
printList(&head); //打印一下看看
}
得到结果
- 遍历打印单链表
从头节开始,打印后继节点,如果后继节点为空就停止打印(这样就打印了每一个节点)
void printList(Node head) {
while (head->next) {
head = head->next;//迭代节点
printf("%d ", head->element);
}
printf("\n");
}
- 删除指定节点
与插入同理,先找到前驱,再让前驱指向,待删除节点的后继后把带删除节点free掉即可
_Bool deleteList(Node pre,int index){
if(index < 1) return 0;
while(--index){
pre = pre->next;
if(pre == NULL){
return 0;
}
}
if(pre->next == NULL) return 0;
Node temp = pre->next;
pre->next = temp->next;
free(temp);
return 1;
}
咱测试一下
int main() {
struct ListNode head;
initList(&head);
for (int i = 0; i < 3; ++i) {
insertList(&head, (i+1) * 100, 1); //依次插入3个元素
}
printList(&head); //打印一下看看
deleteList(&head,1);
printList(&head);
}
- 查找存有指定元素的序号
int findList(Node head,E element){
int index = 1;//位序计数器
while(head->next){//用下一个节点做测试条件
head = head->next;//进来后将下一个节点赋给当前节点,if测试下一个节点,因为当前节点已经再上一次循环测试过了
if(head->element == element){
return index;
}
index++;
}
return -1;//走到链表尾部就说明没找到
}
- 获取指定序号的所存的元素
E * getList(Node head,int index){
if(index < 1) return 0;
while(index--){
head = head->next;
if(head == NULL){
return 0;
}
}
return &head->element;
}
- 获取链表长度(不包含头节点)
int sizeList(Node head){
int index = 0;
while(head->next){//用下一个节点做测试条件
head = head->next;
index++;
}
return index;
}
咱们测试一下
int main() {
struct ListNode head;
initList(&head);
for (int i = 0; i < 3; ++i) {
insertList(&head, (i+1) * 100, 1); //依次插入3个元素
}
printList(&head); //打印一下看看
printf("%d\n", sizeList(&head));
deleteList(&head,1);
printList(&head);
printf("%d\n",sizeList(&head));
}
- 完整代码如下
#include <stdio.h>
#include <stdlib.h>
typedef int E;
struct ListNode{
E element;
struct ListNode * next;//指向下一个节点,建立联系,实现逻辑意义上的相邻
};
typedef struct ListNode* Node;
//头节点初始化
void initList(Node node){
node->next = NULL;
}
_Bool insertList(Node current,E element,int index){
if(index < 1) return 0;
while(--index){
current = current->next;
if(current == NULL){
return 0;
}
}
Node node = malloc(sizeof(struct ListNode));
if(node == NULL) return 0;
node->element = element;
node->next = current->next;
current->next = node;
return 1;
}
void printList(Node head) {
while (head->next) {
head = head->next;
printf("%d ", head->element);
}
printf("\n");
}
_Bool deleteList(Node pre,int index){
if(index < 1) return 0;
while(--index){
pre = pre->next;
if(pre == NULL){
return 0;
}
}
if(pre->next == NULL) return 0;
Node temp = pre->next;
pre->next = temp->next;
free(temp);
return 1;
}
E * getList(Node head,int index){
if(index < 1) return 0;
while(index--){
head = head->next;
if(head == NULL){
return 0;
}
}
return &head->element;
}
int findList(Node head,E element){
int index = 1;//位序计数器
while(head->next){//用下一个节点做测试条件
head = head->next;//进来后将下一个节点赋给当前节点,if测试下一个节点,因为当前节点已经再上一次循环测试过了
if(head->element == element){
return index;
}
index++;
}
return -1;//走到链表尾部就说明没找到
}
int sizeList(Node head){
int index = 0;
while(head->next){//用下一个节点做测试条件
head = head->next;
index++;
}
return index;
}
总结
- 链表的删除操作很快,与链表长度无关。想象一下:你拿着一条项链的一个环,如何拆掉这个环?只需要拆开与它相邻的两个环,然后把相邻的两环接到一起即可。无论项链长短,拆除的操作没有区别。
- 链表的插入也很快,与链表长度无关。
- 链表的查找需要从头遍历,与数组类似,越长速度越慢。但由于没有下标可用,链表的遍历实际上比数组更慢一些。而且在插入、删除节点时,要先获取到必要的指针,才能删除或添加。
- 所以,理论上,在需要经常插入节点、删除节点,而且数据量比较大的场合,非常适合使用链表。
好滴!本次分享到这就结束了
如果对铁汁你有帮助的话,记得点赞👍+收藏⭐️+关注➕
我在这先行拜谢了:)