单链表的定义与结构
单链表由节点(Node)组成,每个节点包含两个部分:数据域和指针域。数据域存储实际数据,指针域存储下一个节点的地址。C语言中通常用结构体定义:
typedef struct Node {
int data; // 数据域,假设存储整型
struct Node *next; // 指针域
} Node;
创建链表
动态内存分配创建链表头节点,初始时链表为空:
该函数用于创建一个空链表的头节点。头节点不存储实际数据,仅作为链表起始位置的标记。头节点的next
指针初始化为NULL
,表示链表为空。
Node* createList() {
// 分配头节点内存空间
Node *head = (Node*)malloc(sizeof(Node));
// 检查内存是否分配成功
if (head == NULL) {
printf("Memory allocation failed\n");
exit(1); // 分配失败时强制退出程序
}
// 初始化头节点的指针域为NULL
head->next = NULL;
// 返回创建好的头节点
return head;
}
插入节点
头插法:新节点插入在链表头部。
这是一个用于在带头节点的单链表头部插入新节点的函数。函数接受链表头指针和要插入的数据作为参数,创建新节点并将其插入到头节点之后的位置。
// 在链表头部插入新节点的函数
// 参数:
// head - 指向链表头节点的指针
// data - 要插入的新节点的数据值
void insertAtHead(Node *head, int data) {
// 为新节点动态分配内存空间
Node *newNode = (Node*)malloc(sizeof(Node));
// 设置新节点的数据域
newNode->data = data;
// 将新节点的next指针指向原头节点之后的节点
newNode->next = head->next;
// 将头节点的next指针指向新节点
head->next = newNode;
}
- 该函数假设传入的head指针不为NULL,且指向一个有效的链表
- 如果链表为空(只有头节点),函数会将新节点作为第一个实际节点插入
- 使用malloc分配内存后,在实际应用中应该检查分配是否成功
- 调用者需要负责最终释放链表内存
尾插法:新节点插入在链表尾部。
该函数实现向单链表尾部插入新节点的功能。函数接收链表头指针head
和要插入的数据data
作为参数。
// 在链表尾部插入新节点的函数
// 参数:head - 链表头节点指针,data - 要插入的数据
void insertAtTail(Node *head, int data) {
// 为新节点分配内存空间
Node *newNode = (Node*)malloc(sizeof(Node));
// 设置新节点的数据域和指针域
newNode->data = data; // 存储数据
newNode->next = NULL; // 新节点作为尾节点,next置为NULL
// 从头节点开始遍历链表
Node *current = head;
// 循环找到当前链表的最后一个节点
// 当current->next为NULL时,current就是尾节点
while (current->next != NULL) {
current = current->next;
}
// 将新节点链接到链表尾部
current->next = newNode;
}
- 该函数假设链表已经有一个头节点
- 头节点的data字段可能未使用或用作特殊用途
- 调用该函数后,新节点将成为链表的第一个实际数据节点
- 需要确保传入的head指针不为NULL
- 使用malloc分配内存后没有检查是否成功,实际应用中应添加错误处理
指定位置插入:在链表的第pos个位置插入节点(从1开始计数)。
该函数实现在单链表的指定位置插入一个新节点。位置计数从1开始,即pos=1表示在头节点后插入。
// 在链表的指定位置插入一个新节点
// 参数说明:
// head: 链表头节点的指针
// pos: 要插入的位置(从1开始计数)
// data: 新节点的数据值
void insertAtPosition(Node *head, int pos, int data) {
// 为新节点分配内存空间
Node *newNode = (Node*)malloc(sizeof(Node));
// 设置新节点的数据值
newNode->data = data;
// 初始化当前节点指针为头节点
Node *current = head;
// 遍历链表,找到要插入位置的前一个节点
// 循环条件:i < pos(因为位置从1开始计数)
// 同时确保当前节点不为空(防止越界)
for (int i = 1; i < pos && current != NULL; i++) {
current = current->next;
}
// 如果当前节点为空,说明位置无效
if (current == NULL) {
printf("Invalid position\n");
return;
}
// 将新节点的next指针指向当前节点的下一个节点
newNode->next = current->next;
// 将当前节点的next指针指向新节点,完成插入操作
current->next = newNode;
}
- 该函数没有处理pos=0的情况(在头节点前插入)
- 函数假设链表至少有一个头节点
- 当pos超过链表长度时,会打印"Invalid position"并返回
- 调用者需要确保传入有效的head指针
删除节点
删除头节点:删除链表的第一个有效节点(非头节点)。
void deleteAtHead(Node *head) {
// 检查链表是否为空(头哨兵节点的next为空)
if (head->next == NULL) {
printf("List is empty\n");
return;
}
// 保存待删除的节点(头哨兵节点的下一个节点)
Node *temp = head->next;
// 更新头哨兵节点的next指针,跳过待删除节点
head->next = temp->next;
// 释放待删除节点的内存
free(temp);
}
删除尾节点:删除链表的最后一个节点。
void deleteAtTail(Node *head) {
// 检查链表是否为空(仅包含头节点)
if (head->next == NULL) {
printf("List is empty\n");
return;
}
// 初始化指针,从头节点开始遍历
Node *current = head;
// 遍历到倒数第二个节点
while (current->next->next != NULL) {
current = current->next;
}
// 释放尾节点的内存
free(current->next);
// 将倒数第二个节点的next指针置为NULL
current->next = NULL;
}
- 该实现假设链表包含头节点
- 调用前需确保头节点本身不为NULL
- 删除操作后链表长度减1,但头节点保留
删除指定位置节点:删除链表的第pos个节点。
void deleteAtPosition(Node *head, int pos) {
// 检查链表是否为空(仅含头节点)
if (head->next == NULL) {
printf("List is empty\n");
return;
}
// 遍历链表直到目标位置的前驱节点
Node *current = head;
for (int i = 1; i < pos && current->next != NULL; i++) {
current = current->next;
}
// 检查位置是否超出链表范围
if (current->next == NULL) {
printf("Invalid position\n");
return;
}
// 执行删除操作
Node *temp = current->next; // 保存待删除节点
current->next = temp->next; // 前驱节点跳过待删除节点
free(temp); // 释放内存
}
- 该实现假设头节点不存储有效数据(哑节点设计)
- 位置参数
pos
从1开始计数,对应第一个有效节点 - 当链表为空或位置无效时,函数会打印错误信息并返回
- 删除操作的时间复杂度为O(n),需要遍历链表找到前驱节点
查找节点
按值查找:返回第一个匹配值的节点位置,未找到返回-1。
/**
* 在链表中搜索指定值的位置
* @param head 链表头节点指针(带哨兵节点)
* @param value 要搜索的目标值
* @return 目标值的位置(从1开始计数),未找到返回-1
*/
int searchByValue(Node *head, int value) {
Node *current = head->next; // 跳过哨兵节点,从第一个有效节点开始
int pos = 1; // 位置计数器初始化为1
while (current != NULL) { // 遍历链表直到末尾
if (current->data == value) { // 找到目标值
return pos; // 返回当前位置
}
current = current->next; // 移动到下一个节点
pos++; // 位置计数器递增
}
return -1; // 遍历结束未找到目标值
}
按位置查找:返回第pos个节点的值。
/**
* 获取链表中指定位置节点的值
* @param head 链表头节点指针(带哨兵节点)
* @param pos 要获取的位置(从1开始计数)
* @return 目标位置节点的值,位置无效返回-1
*/
int searchByPosition(Node *head, int pos) {
Node *current = head->next; // 跳过哨兵节点,从第一个有效节点开始
// 遍历到目标位置或链表末尾
for (int i = 1; i < pos && current != NULL; i++) {
current = current->next;
}
if (current == NULL) { // 位置超出链表范围
printf("Invalid position\n");
return -1;
}
return current->data; // 返回目标位置节点的值
}
遍历链表
打印链表中所有节点的值。
void traverseList(Node *head) {
// 定义一个指针current,初始化为链表的第一个实际节点(跳过头节点)
Node *current = head->next;
// 遍历链表直到current指向NULL(链表末尾)
while (current != NULL) {
// 打印当前节点的数据,并附加" -> "表示指向下一个节点
printf("%d -> ", current->data);
// 移动current指针到下一个节点
current = current->next;
}
// 打印"NULL"表示链表结束
printf("NULL\n");
}
销毁链表
释放链表中所有节点的内存,避免内存泄漏。
void destroyList(Node *head) {
// 初始化当前指针,从头节点的下一个节点开始(跳过头节点)
Node *current = head->next;
// 遍历链表直到末尾(current为NULL)
while (current != NULL) {
// 临时保存当前节点地址用于后续释放内存
Node *temp = current;
// 移动当前指针到下一个节点
current = current->next;
// 释放当前节点占用的内存
free(temp);
}
// 将头节点的next指针置为NULL,表示空链表
head->next = NULL;
}
- 函数不会释放头节点本身的内存,仅释放数据节点
- 调用该函数后,链表变为仅有头节点的空链表
- 若链表本身为空(head->next为NULL),函数直接执行head->next=NULL操作
示例代码
以下是一个完整的单链表操作示例:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node* createList() {
Node *head = (Node*)malloc(sizeof(Node));
head->next = NULL;
return head;
}
void insertAtHead(Node *head, int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = head->next;
head->next = newNode;
}
void traverseList(Node *head) {
Node *current = head->next;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
int main() {
Node *head = createList();
insertAtHead(head, 10);
insertAtHead(head, 20);
insertAtHead(head, 30);
traverseList(head); // 输出: 30 -> 20 -> 10 -> NULL
return 0;
}
以上操作涵盖了单链表的基本功能,实际使用时可根据需求进一步扩展。