数据结构——链表的基本操作
链表
链表的存储结构
typedef struct Node{int data; // 数据域struct Node *next; // 指针域(就是和当前节点同类型的另一个节点)}Node;
链表初始化
- 带头结点
// 一般都采用动态的方式去创建
// 初始化带头结点的链表
Node* InitNode(){Node* head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}
- 不带头结点
Node* InitNode(){return NULL;
}
链表的插入
注:下面的几个基本操作都是带头结点的
- 按位置插入
// 表头插入
void InsertNode(Node* head, int e)
{// 检查头结点是否有效if(head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node)); // 这里还可以加一步看是否初始化成功s->data = e; s->next = head->next;head->next = s;
}// 指定位置插入
// 思路:需要找到目标位置的前驱结点
void InsertNode(Node* head, int i, int e)
{// 检查头结点是否有效if(head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node)); Node* p = head;int count = 0;while(p != NULL && count < i-1){p = p->next;count++;}s->data = e; s->next = p->next;p->next = s;
}
链表的删除
// 按值删除
void DeleteNode(Node* head, int e)
{Node* p = head;while(p != NULL && p->next->data != e){p = p->next; // 待删除结点的前驱结点}if(p->next == NULL){printf("Not find.");return;}Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL; // 避免野指针
}
链表的查找
- 按值查找,返回位置
int FindNode(Node* head, int e)
{Node* p = head;int count = 1;while(p != NULL){if(p->data == e){return count}p = p->next; count++;}return -1 // 返回值是int类型
}
链表的修改
- 按位置修改
void modifyNode(Node* head, int i, int e)
{Node* p = head;int count = 1;while(p != NULL && count < i){p = p->next;count++;}if(p == NULL){printf("Invalid location.");return;}p->data = e;
}
带头结点链表基本操作的完整代码
- 在使用带头结点链表的过程中,一定要正确区分头节点和数据节点,请看下面错误示例
// 错误示例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data; // 数据域struct Node* next; // 指针域
}Node;Node* InitNode() {Node* head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}void InsertNode(Node** head, int i, int e)
{// 检查头结点是否有效if (head == NULL){return;}Node* s = (Node*)malloc(sizeof(Node));Node* p = *head;int count = 0;// 找到指定位置的前驱结点while (p != NULL && count < i - 1){p = p->next;count++;}s->data = e;s->next = p->next;p->next = s;
}//按值删除
void DeleteNode(Node** head, int e)
{Node* p = *head;while (p != NULL && p->next->data != e){p = p->next; // 待删除结点的前驱结点}if (p->next == NULL){printf("Not find.");return;}Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL; // 避免野指针
}// 按位修改
void modifyNode(Node** head, int i, int e)
{Node* p = *head;int count = 1;while (p != NULL && count < i){p = p->next;count++;}if (p == NULL){printf("Invalid location.");return;}p->data = e;
}// 按值查找,返回位置
int FindNode(Node* head, int e)
{Node* p = head;int count = 1;while (p != NULL){if (p->data == e){printf("Find it at position %d\n", count);return count;}p = p->next;count++;}return -1; // 返回值是int类型
}void PrintNode(Node* head) {if (head == NULL) {printf("链表为空\n");return;}Node* p = head;printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode(); // 初始化顺序表// 测试插入操作InsertNode(&head, 1, 10);InsertNode(&head, 2, 20);InsertNode(&head, 3, 30);PrintNode(head);DeleteNode(&head, 30);PrintNode(head); // 应该输出: 10 20 modifyNode(&head, 2, 25);PrintNode(head); // 应该输出: 10 25FindNode(head, 25); // 应该返回: 2return 0;
}
输出:
原因:
- 头节点的
data
未初始化,导致打印出随机垃圾值 - 插入、查找、修改等操作都错误地包含了头节点,导致位置计算偏移
正确代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data; // 数据域struct Node* next; // 指针域
} Node;// 初始化带头节点的链表(头节点不存储实际数据)
Node* InitNode() {Node* head = (Node*)malloc(sizeof(Node));if (head == NULL) {printf("内存分配失败\n");return NULL;}head->next = NULL; // 头节点的next初始化为NULL// 头节点数据域可以不用初始化(因为不使用)return head;
}// 在第i个位置插入元素e(i从1开始,从第一个数据节点计数)
void InsertNode(Node** head, int i, int e) {// 检查参数合法性if (head == NULL || *head == NULL || i < 1) {printf("插入失败:参数不合法或链表未初始化\n");return;}Node* s = (Node*)malloc(sizeof(Node));if (s == NULL) {printf("内存分配失败\n");return;}s->data = e;Node* p = *head; // p指向头节点(前驱节点的起点)int count = 0; // 头节点对应count=0,数据节点从count=1开始// 找到第i-1个数据节点的前驱(头节点或某个数据节点)while (p != NULL && count < i - 1) {p = p->next;count++;}if (p == NULL) {printf("插入位置超出链表长度\n");free(s);return;}// 插入新节点s->next = p->next;p->next = s;
}// 按值删除节点(删除第一个匹配的节点)
void DeleteNode(Node** head, int e) {if (head == NULL || *head == NULL || (*head)->next == NULL) {printf("删除失败:链表为空或未初始化\n");return;}Node* p = *head; // p指向头节点(从这里开始查找前驱)// 查找待删除节点的前驱(停在待删除节点的前一个节点)while (p->next != NULL && p->next->data != e) {p = p->next;}if (p->next == NULL) {printf("未找到值为%d的节点\n", e);return;}// 删除节点Node* temp = p->next;p->next = temp->next;free(temp);temp = NULL;
}// 按位置修改节点值(i从1开始,从第一个数据节点计数)
void modifyNode(Node** head, int i, int e) {if (head == NULL || *head == NULL || (*head)->next == NULL || i < 1) {printf("修改失败:链表为空或位置不合法\n");return;}Node* p = (*head)->next; // 直接指向第一个数据节点int count = 1; // 数据节点从1开始计数// 找到第i个数据节点while (p != NULL && count < i) {p = p->next;count++;}if (p == NULL) {printf("修改位置超出链表长度\n");return;}p->data = e;
}// 按值查找,返回位置(从1开始,从第一个数据节点计数)
int FindNode(Node* head, int e) {if (head == NULL || head->next == NULL) {printf("查找失败:链表为空\n");return -1;}Node* p = head->next; // 跳过头节点,从第一个数据节点开始int count = 1; // 数据节点从1开始计数while (p != NULL) {if (p->data == e) {printf("Find it at position %d\n", count);return count;}p = p->next;count++;}printf("未找到值为%d的节点\n", e);return -1;
}// 打印所有数据节点(跳过头节点)
void PrintNode(Node* head) {if (head == NULL || head->next == NULL) {printf("链表为空\n");return;}Node* p = head->next; // 跳过头节点,从第一个数据节点开始打印printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode(); // 初始化带头节点的链表// 测试插入操作InsertNode(&head, 1, 10); // 位置1插入10InsertNode(&head, 2, 20); // 位置2插入20InsertNode(&head, 3, 30); // 位置3插入30PrintNode(head); // 输出:链表元素: 10 20 30// 测试删除操作DeleteNode(&head, 30);PrintNode(head); // 输出:链表元素: 10 20// 测试修改操作modifyNode(&head, 2, 25);PrintNode(head); // 输出:链表元素: 10 25// 测试查找操作FindNode(head, 25); // 输出:Find it at position 2return 0;
}
注:这里再说明一个问题:
head == NULL || *head == NULL
这个检查是很有必要的,是对两种错误场景进行检查
head == NULL
:表示传递给函数的二级指针本身就是无效的(比如调用者错误地传入了NULL
作为参数,如InsertNode(NULL, 1, 10)
)。这种情况下,连head
都不能访问(会直接触发空指针解引用错误),必须优先检查。head == NULL
:表示二级指针head
本身有效(非空),但它指向的头节点指针是无效的(比如链表未初始化)。这种情况下,head
是合法指针,但head
是空指针,需要单独处理。
无头结点链表的增删改查
- 注:这里使用了二级指针,不同于之前的顺序表,其操作的是结构体,当我们传递
SqList* L
(一级指针)时,已经可以通过指针访问并修改结构体内部的所有成员(数组和长度) - 而链表为什么使用二级指针的原因是:C语言的函数参数传递是值传递—函数接收的是参数的副本,而非参数,所以如果使用一级指针(
Node*
)作为参数,函数内部修改的只是头指针的副本,无法影响外部实际的头指针。只有通过二级指针(Node**
),才能真正修改外部头指针的指向
// 错误示例:使用一级指针
void InsertAtHead(Node* head, int value) {Node* newNode = createNode(value);newNode->next = head;head = newNode; // 这里修改的只是函数内部的副本,外部头指针不变
}// 正确示例:使用二级指针
void InsertAtHead(Node** head, int value) {Node* newNode = createNode(value);newNode->next = *head; // *head表示外部的头指针*head = newNode; // 直接修改外部头指针的指向
}
- 如果操作会改变头指针的指向,就必须用二级指针;如果只是修改节点内部的数据或 next 指针,一级指针就足够了
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>typedef struct Node {int data; // 数据域struct Node* next; // 指针域
}Node;Node* InitNode() {return NULL;
}// 使用二级指针才能修改头指针
void InsertNode(Node** head, int i, int e)
{// 判断位置是否合法while (i < 1){return;}Node* s = (Node*)malloc(sizeof(Node));s->data = e;// 表头插入if (i == 1){s->next = *head;*head = s;return;}Node* p = *head;int count = 1;while (p != NULL && count < i - 1){p = p->next;count++;}if (p == NULL) {printf("插入位置超出链表长度\n");free(s); // 释放已分配的内存return;}s->next = p->next;p->next = s;return;}//按值删除
void DeleteNode(Node** head, int e)
{// 处理空链表if(*head == NULL){printf("Empty list.");return;}Node* p = *head;Node* prev = NULL;// 查找要删除的结点及其前驱结点while (p != NULL && p->data != e){prev = p;p = p->next;}// 未找到if(p == NULL){printf("Not find.");return;}// 处理头结点的删除if(prev == NULL){*head = p->next;}else {prev->next = p->next;}free(p);p = NULL; // 避免野指针
}// 按位修改
bool UpdateNodeByIndex(Node** head, int i, int e)
{if(*head == NULL || i < 1){printf("Empty list or invalid position.");return false;}Node* p = *head;int count = 1;while(p != NULL && count < i){p = p->next;count++;}p->data = e;return true;
}void PrintNode(Node* head) {if (head == NULL) {printf("链表为空\n");return;}Node* p = head;printf("链表元素: ");while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}int main() {Node* head = InitNode(); // 初始化顺序表// 测试插入操作InsertNode(&head, 1, 10);InsertNode(&head, 2, 20);InsertNode(&head, 3, 30);PrintNode(head);DeleteNode(&head, 30);PrintNode(head); // 应该输出: 10 20 UpdateNodeByIndex(&head, 2, 25);PrintNode(head); // 应该输出: 10 25return 0;
}