链表(上):单向链表的定义、创建、遍历、插入与删除节点
各位同学,今天我们来聊聊链表。说实话,链表这东西,我当年刚学C语言的时候也觉得挺绕的。但后来在工作中,尤其是做嵌入式系统开发时,才发现它真的是个宝贝。数组虽然简单,但你要在中间插个数据,那叫一个麻烦——得把后面的元素全往后挪。链表就不一样了,它像一串手拉手的小朋友,想插队?改一下前后两个人的手就行了。
单向链表的结构定义
先看最基础的东西。单向链表,每个节点包含两部分:数据域和指针域。指针域指向下一个节点,最后一个节点指向NULL。
typedef struct Node {
int data; // 数据域
struct Node *next; // 指针域,指向下一个节点
} Node;
这里有个细节我提醒一下:struct Node *next 不能写成 Node *next,因为typedef还没生效呢。嗯,这个坑我踩过。
创建节点与链表
创建节点说白了就是malloc一块内存,填上数据,把next置NULL。我习惯写一个辅助函数:
Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
return NULL;
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
创建链表通常有两种方式:头插法和尾插法。头插法新节点总是插在头部,尾插法则保持原有顺序。我个人更常用尾插法,因为更符合直觉。
Node* createList(int arr[], int n) {
Node *head = NULL, *tail = NULL;
for (int i = 0; i < n; i++) {
Node *newNode = createNode(arr[i]);
if (head == NULL) {
head = tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
}
return head;
}
遍历链表
遍历很简单,从头节点开始,一路next到底。你想想看,这不就是顺着绳子摸瓜吗?
void traverseList(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
这里有个小技巧:遍历时不要直接用head指针移动,否则你就找不到链表头了。用个临时指针current来跑。
插入节点
插入分三种情况:头部插入、尾部插入、中间插入。中间插入最考验基本功,因为要处理前后两个指针。
// 在指定位置之后插入
int insertAfter(Node *prev, int data) {
if (prev == NULL) return -1;
Node *newNode = createNode(data);
if (newNode == NULL) return -1;
newNode->next = prev->next;
prev->next = newNode;
return 0;
}
删除节点
删除比插入稍微麻烦一点,因为你要找到待删节点的前一个节点。单向链表只能往前走,不能回头,所以得用一个prev指针跟着。
int deleteNode(Node **head, int target) {
if (*head == NULL) return -1;
Node *current = *head;
Node *prev = NULL;
// 如果头节点就是要删除的
if (current->data == target) {
*head = current->next;
free(current);
return 0;
}
// 查找目标节点
while (current != NULL && current->data != target) {
prev = current;
current = current->next;
}
if (current == NULL) return -1; // 没找到
prev->next = current->next;
free(current);
return 0;
}
知识体系总览
下面这张图把单向链表的操作串起来了,你可以对照着看:
常见错误与避坑指南
我总结几个新手最容易犯的错误,你写代码的时候多留个心眼:
- 忘记初始化next指针——malloc出来的内存是脏的,不置NULL的话,遍历时会越界。
- 插入时指针顺序搞反——先让新节点指后面,再让前面节点指新节点。顺序错了,后面的节点就找不到了。
- 删除头节点时没更新head——如果你删的是头节点,记得把head指向下一个节点。
- free之后继续使用——释放后记得把指针置NULL,否则就是野指针。
我的经验之谈:写链表操作时,我习惯先在纸上画图。把每个节点画成方框,箭头标清楚,再动手写代码。这样逻辑清晰,不容易出错。你试试看,真的管用。
好了,单向链表的基础操作就这些。说白了就是:定义结构、创建节点、遍历访问、插入删除。每个操作都不复杂,但组合起来就能解决很多实际问题。下一节我们会继续深入,看看双向链表和循环链表怎么玩。
公众号:蓝海资料掘金营,微信deep3321