手动实现单链表操作
一、头文件:Linked_list.h
#ifndef LINKED_LIST_H // 头文件保护宏,防止重复包含
#define LINKED_LIST_H
// 包含常用头文件
#include <stdbool.h> // 布尔类型
#include <stdlib.h> // 内存管理函数(malloc/calloc/free)
#include <stdio.h> // 输入输出函数
// 定义数据类型别名(方便后续修改数据类型)
typedef int DataType;
// 定义链表结点结构
typedef struct node {
DataType data; // 数据域,存储具体数据
struct node *next; // 指针域,指向下一个结点
} Node;
// 定义链表结构体(管理链表整体信息)
typedef struct {
Node *head; // 头指针,指向链表第一个结点
Node *tail; // 尾指针,指向链表最后一个结点
int size; // 链表长度(结点个数)
} LinkedList;
// 函数声明列表
// 创建空链表
// 返回值:指向新创建链表的指针
LinkedList *create_linked_list();
// 销毁链表(释放所有内存)
// 参数:链表指针
void destroy_linked_list(LinkedList *list);
// 头插法:在头部插入新结点
// 参数:链表指针、新数据
// 返回值:插入成功与否(布尔值)
bool add_before_head(LinkedList *list, DataType new_data);
// 尾插法:在尾部插入新结点
// 参数:链表指针、新数据
// 返回值:插入成功与否(布尔值)
bool add_behind_tail(LinkedList *list, DataType new_data);
// 按索引插入结点(0-based索引)
// 参数:链表指针、索引、新数据
// 返回值:插入成功与否(布尔值)
bool add_by_idx(LinkedList *list, int idx, DataType new_data);
// 按索引搜索结点
// 参数:链表指针、索引
// 返回值:目标结点指针(失败返回NULL)
Node *search_by_idx(LinkedList *list, int idx);
// 按数据值搜索结点(返回首个匹配结点)
// 参数:链表指针、目标数据
// 返回值:目标结点指针(失败返回NULL)
Node *search_by_data(LinkedList *list, DataType data);
// 按数据值删除首个匹配结点
// 参数:链表指针、目标数据
// 返回值:删除成功与否(布尔值)
bool delete_by_data(LinkedList *list, DataType data);
// 扩展:按索引删除结点(0-based索引)
// 参数:链表指针、索引
// 返回值:删除成功与否(布尔值)
bool delete_by_idx(LinkedList *list, int idx);
// 打印链表内容(格式:1 -> 2 -> 3 -> ...)
// 参数:链表指针
void print_list(LinkedList *list);
#endif // !LINKED_LIST_H
#pragma once // 替代头文件保护宏的另一种方式(部分编译器支持)
二、实现文件:linked_list.c
#include "Linked_list.h" // 包含自定义头文件
// 创建空链表
// 功能:分配链表结构体内存,初始化头/尾指针和长度
LinkedList *create_linked_list() {
// calloc自动初始化内存为0,链表初始时head/tail为NULL,size为0
return calloc(1, sizeof(LinkedList));
}
// 销毁链表
// 功能:释放所有结点内存,再释放链表结构体内存
void destroy_linked_list(LinkedList *list) {
Node *curr = list->head; // 从头部开始遍历
while (curr != NULL) { // 循环直到所有结点处理完毕
Node *temp = curr->next; // 暂存下一个结点指针
free(curr); // 释放当前结点内存
curr = temp; // 移动到下一个结点
}
free(list); // 释放链表结构体内存
}
// 打印链表
// 功能:按格式输出链表所有结点数据
void print_list(LinkedList *list) {
Node *curr = list->head; // 从头部开始遍历
while (curr != NULL) {
printf("%d", curr->data); // 输出当前数据
if (curr->next != NULL) { // 非最后一个结点时添加箭头
printf("->");
}
curr = curr->next; // 移动到下一个结点
}
printf("\n"); // 换行
}
// 头插法实现
bool add_before_head(LinkedList *list, DataType new_data) {
Node *new_node = (Node *)malloc(sizeof(Node)); // 分配新结点内存
if (new_node == NULL) { // 内存分配失败处理
printf("内存分配失败");
exit(-1); // 终止程序(实际开发中可返回错误码)
}
new_node->data = new_data; // 赋值数据域
new_node->next = list->head; // 新结点的next指向原头部
list->head = new_node; // 头指针指向新结点
// 处理空链表情况:插入后尾指针也指向新结点
if (list->tail == NULL) {
list->tail = new_node;
}
list->size++; // 长度+1
return true;
}
// 尾插法实现
bool add_behind_tail(LinkedList *list, DataType new_data) {
Node *new_node = malloc(sizeof(Node)); // 分配新结点内存
if (new_node == NULL) {
printf("内存分配失败");
exit(-1);
}
new_node->data = new_data; // 赋值数据域
new_node->next = NULL; // 尾结点的next必须为NULL
// 处理空链表情况:头/尾指针均指向新结点
if (list->tail == NULL) {
list->head = new_node;
list->tail = new_node;
} else { // 非空链表:原尾结点的next指向新结点,更新尾指针
list->tail->next = new_node;
list->tail = new_node;
}
list->size++; // 长度+1
return true;
}
// 按索引插入实现
bool add_by_idx(LinkedList *list, int idx, DataType new_data) {
// 索引合法性检查(idx范围:0 ~ size)
if (idx < 0 || idx > list->size) {
printf("输入的索引错误\n");
return false;
}
// 特殊情况:索引为0(头插法)或索引为size(尾插法)
if (idx == 0) {
return add_before_head(list, new_data);
}
if (idx == list->size) {
return add_behind_tail(list, new_data);
}
// 普通情况:找到索引idx-1的结点(前驱结点)
Node *new_node = malloc(sizeof(Node)); // 分配新结点内存
if (new_node == NULL) {
printf("内存分配失败");
return false;
}
new_node->data = new_data; // 赋值数据域
Node *prev = list->head; // 从头部开始遍历
// 移动prev到idx-1的位置
for (int i = 0; i < idx - 1; i++) {
prev = prev->next;
}
// 插入逻辑:新结点的next指向prev的下一个结点,prev的next指向新结点
new_node->next = prev->next;
prev->next = new_node;
list->size++; // 长度+1
return true;
}
// 按索引搜索实现
Node *search_by_idx(LinkedList *list, int idx) {
// 索引合法性检查(idx范围:0 ~ size-1)
if (idx < 0 || idx > list->size - 1) {
printf("索引不合法");
return NULL;
}
Node *curr = list->head; // 从头部开始遍历
// 移动curr到idx的位置
for (int i = 0; i < idx; i++) {
curr = curr->next;
}
return curr; // 返回目标结点
}
// 按数据值搜索实现(返回首个匹配结点)
Node *search_by_data(LinkedList *list, DataType data) {
Node *curr = list->head; // 从头部开始遍历
while (curr != NULL) { // 遍历直到链表末尾
if (curr->data == data) { // 匹配成功,返回当前结点
return curr;
}
curr = curr->next; // 移动到下一个结点
}
return NULL; // 未找到匹配结点
}
// 按数据值删除实现(删除首个匹配结点)
bool delete_by_data(LinkedList *list, DataType data) {
Node *curr = list->head; // 当前结点
// 处理头结点匹配的情况
if (curr != NULL && curr->data == data) {
list->head = curr->next; // 头指针指向下一个结点
if (list->head == NULL) { // 删除后链表为空,更新尾指针
list->tail = NULL;
}
free(curr); // 释放头结点内存
list->size--; // 长度-1
return true;
}
// 查找后续结点中的匹配项
Node *prev = curr; // 前驱结点
curr = curr->next; // 当前结点后移一位
while (curr != NULL) {
if (curr->data == data) { // 找到匹配结点
prev->next = curr->next; // 前驱结点跳过当前结点
if (curr->next == NULL) { // 删除的是尾结点,更新尾指针
list->tail = prev;
}
free(curr); // 释放当前结点内存
list->size--; // 长度-1
return true;
}
prev = curr; // 前驱后移
curr = curr->next; // 当前后移
}
return false; // 未找到匹配结点
}
// 按索引删除实现
bool delete_by_idx(LinkedList *list, int idx) {
// 索引合法性检查(idx范围:0 ~ size-1)
if (idx < 0 || idx >= list->size) {
printf("索引错误");
return false;
}
Node *curr = list->head; // 当前结点
Node *prev = NULL; // 前驱结点
// 处理删除头结点的情况(idx=0)
if (idx == 0) {
list->head = curr->next; // 头指针指向下一个结点
if (list->size == 1) { // 删除后链表为空,更新尾指针
list->tail = NULL;
}
free(curr); // 释放头结点内存
list->size--; // 长度-1
return true;
}
// 查找索引idx的结点(移动curr到idx位置,prev到idx-1位置)
for (int i = 0; i < idx; i++) {
prev = curr;
curr = curr->next;
}
prev->next = curr->next; // 前驱结点跳过当前结点
if (idx == list->size - 1) { // 删除的是尾结点,更新尾指针
list->tail = prev;
}
free(curr); // 释放当前结点内存
list->size--; // 长度-1
return true;
}
三、主函数:main.c
#define _CRT_SECURE_NO_WARNINGS // 禁用VS编译器的安全警告(可选)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 计算数组长度的宏(方便数组操作)
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#include "Linked_list.h" // 包含链表头文件
int main(void) {
// 1. 创建链表
LinkedList *list = create_linked_list();
// 2. 头插法测试
add_before_head(list, 10);
add_before_head(list, 20);
add_before_head(list, 30);
printf("在头部插入30, 20, 10后:\n");
print_list(list); // 输出:30->20->10
// 3. 尾插法测试
add_behind_tail(list, 40);
add_behind_tail(list, 50);
printf("在尾部插入40, 50后:\n");
print_list(list); // 输出:30->20->10->40->50
// 4. 按索引插入测试(索引2插入25)
add_by_idx(list, 2, 25);
printf("在索引2插入25后:\n");
print_list(list); // 输出:30->20->25->10->40->50
// 5. 按索引搜索测试(获取索引2的结点)
Node *node = search_by_idx(list, 2);
printf("索引2处的元素: %d\n", node->data); // 输出:25
// 6. 按数据值搜索测试(查找值为40的结点)
node = search_by_data(list, 40);
printf("找到数据40的结点: %d\n", node->data); // 输出:40
// 7. 按数据值删除测试(删除值为25的结点)
delete_by_data(list, 25);
printf("删除数据25后:\n");
print_list(list); // 输出:30->20->10->40->50
// 8. 按索引删除测试(删除索引0的结点,即30)
delete_by_idx(list, 0);
printf("删除索引0处的元素后:\n");
print_list(list); // 输出:20->10->40->50
// 9. 销毁链表(释放内存)
destroy_linked_list(list);
return 0;
}
四、关键知识点总结
-
链表结构:
- 每个结点包含数据域和指针域(指向下一个结点)。
- 链表结构体管理头指针、尾指针和长度,方便快速操作头尾结点。
-
内存管理:
calloc
用于创建链表结构体(自动初始化内存为0)。malloc
用于分配结点内存,需手动初始化next
指针(尾插法中需设为NULL
)。- 销毁链表时需先释放所有结点内存,再释放链表结构体内存,避免内存泄漏。
-
核心操作:
- 头插法:时间复杂度 O(1),更新头指针和尾指针(空链表时)。
- 尾插法:时间复杂度 O(1)(利用尾指针),需处理空链表情况。
- 按索引插入/删除:需先找到前驱结点,时间复杂度 O(n)(n为索引值)。
- 搜索操作:按索引搜索 O(n),按值搜索 O(n)(需遍历链表)。
-
边界处理:
- 插入/删除时需检查索引合法性(如
idx=0
或idx=size
的情况)。 - 空链表或单个结点时,头指针和尾指针需同步更新(如删除头结点后若链表为空,尾指针需置
NULL
)。
- 插入/删除时需检查索引合法性(如