1.链表的定义
链表是一种动态存储的线性数据结构,通过节点指针关联离散的内存块。其核心特点是:
“节点本身不存储大量数据,而是作为数据挂载的钩子”
我们可能会觉得用一个数组去存储这些变量就好了,为什么要多引出一个“链表”?
数组是固定内存,如果你要插入一个元素到数组中间,你需要把后面的元素全部往后移,然后再插进去;如果数组分配的内存小了,则插不进去了,大了又浪费内存。
而链表能够完美解决数组在插入、扩容和内存利用上的痛点,核心在于其动态内存分配和指针重定向机制。相比数组主要有以下两个优势:
1、无需移动后续元素
2、动态内存分配(合理使用内存中的非连续空间)
2.单向链表
2.1 链表结构
代码部分解析:
在链表中都会有数据域和指针域
struct LNode {
int data;//数据域
struct LNode *next;//指针域
};
数据域:可存放该节点的任何数据类型值。
指针域:指向下一个节点的引用指针。
创建3个链表指针
struct LNode *head,*middle,*last;
指向对应节点
head = (LNode *)malloc(sizeof(struct LNode));
middle = (LNode *)malloc(sizeof(struct LNode));
last = (LNode *)malloc(sizeof(struct LNode));
给各个节点数据域data赋值
head -> data = 10;
middle -> data =20;
last -> data =30;
将节点连接起来
head的next指向middle,并且会将middle的地址2024存到head的next,其他以此类推。
last的后续没有节点,所以last的next为null
head->next = middle ;
middle->next = last;
last->next = NULL;
最后应该是这个样
2.2 通过头指针获取链表中的值
第一个循环
struct LNode *temp = head;//创建一个临时指针temp,把头结点指针赋值给temp
while(temp != NULL){//temp此时不等于NULL,满足条件,进入循环
printf("%d ",temp->data);//输出序列“10”
temp = temp->next;//把temp移到下一个节点2024
}
代码执行一遍之后,再进while,检查temp是否为NULL。不为NULL,满足条件,进入下一次遍历。
第二次循环
struct LNode *temp = head;//创建一个临时指针temp,把头结点指针赋值给temp
while(temp != NULL){//temp此时不等于NULL,满足条件,进入循环
printf("%d ",temp->data);//输出序列“20”
temp = temp->next;//把temp移到下一个节点3024
}
此时temp指向3024这个地址。
检查temp还是不为NULL,进入下一个遍历。
第三次循环
struct LNode *temp = head;//创建一个临时指针temp,把头结点指针赋值给temp
while(temp != NULL){//temp此时不等于NULL,满足条件,进入循环
printf("%d ",temp->data);//输出序列“30”
temp = temp->next;//把temp移到下一个节点NULL
}
temp = NULL,不满足while里面的条件,跳出循环,从而打出三个值。
2.3 单链表操作
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
struct LNode {
int data; // 节点存储的数据
struct LNode* next; // 指向下一个节点的指针
};
// 初始化空链表
struct LNode* head = NULL;
// 头插法插入节点
void insertHead(int e) {
// 分配内存给新节点
struct LNode* newNode = (struct LNode*)malloc(sizeof(struct LNode));
if (newNode == NULL) {
printf("Memory allocation failed\n"); // 内存分配失败处理
return;
}
// 设置新节点的数据和指向原头结点的指针
newNode->data = e;
newNode->next = head;//也就是记录上一个节点的地址
// 更新头指针地址
head = newNode;
}
// 尾插法插入节点
void insertTail(int e) {
// 分配内存给新节点
struct LNode* newNode = (struct LNode*)malloc(sizeof(struct LNode));
if (newNode == NULL) {
printf("Memory allocation failed\n"); // 内存分配失败处理
return;
}
// 设置新节点的数据和指针
newNode->data = e;
newNode->next = NULL;
// 如果链表为空,则头指针指向新节点
if (head == NULL) {
head = newNode;
return;
}
// 否则找到尾节点并将其next指向新节点
struct LNode* tail = head;
while (tail->next != NULL) {//用尾结点的特性找尾节点
tail = tail->next;
}
tail->next = newNode;
}
// 删除指定值的节点
void deleteNode(int key) {
struct LNode* temp = head;
struct LNode* prev = NULL;
// 如果头节点就是要删除的节点
if (temp != NULL && temp->data == key) {
head = temp->next; // 修改头指针
free(temp); // 释放内存
return;
}
// 查找要删除的节点
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
// 如果找不到该节点
if (temp == NULL) {
printf("Key not found in the list\n");
return;
}
// 跳过要删除的节点
prev->next = temp->next;
free(temp); // 释放内存
}
// 搜索指定值的节点
struct LNode* searchNode(int key) {
struct LNode* current = head;
while (current != NULL) {
if (current->data == key) {
return current; // 返回找到的节点
}
current = current->next;
}
return NULL; // 如果没有找到返回NULL
}
// 打印链表
void printList() {
struct LNode* current = head;
while (current != NULL) {
printf("%d -> ", current->data); // 打印当前节点数据
current = current->next;
}
printf("NULL\n"); // 表示链表结束
}
int main() {
// 使用头插法插入节点
insertHead(10);
insertHead(20);
insertHead(30);
printf("After inserting nodes at head:\n");
printList(); // 打印链表
// 使用尾插法插入节点
insertTail(40);
insertTail(50);
printf("\nAfter inserting nodes at tail:\n");
printList(); // 打印链表
// 删除指定值的节点
deleteNode(20);
printf("\nAfter deleting node with value 20:\n");
printList(); // 打印链表
// 搜索指定值的节点
struct LNode* searchedNode = searchNode(30);
if (searchedNode != NULL) {
printf("\nFound node with value %d\n", searchedNode->data); // 打印找到的节点数据
} else {
printf("\nNode not found\n");
}
return 0;
}
学习抽象过程的笔记如下,看完之后再去理解前面的代码会好一点
在这里:指向 = 具体节点
是一种做题技巧,帮助理解。例如
p=q:p指向q
p=q->next:p指向q后面哪一个小方格
p->next=q:p的后面那个小方格指向q(也可以理解为圈起来的那根线)
p->next=q->next:p后面的小方格指向q的下一个地址
p=p->next:p指向p的下一个地址,也就是p的下一个地址赋值给p
用以上的方法理解
单链表插入
单链表删除与头插法