数组因其连续存储和随机访问的特性,成为最基础的数据结构之一。然而,静态分配的局限使其在处理动态数据时面临挑战:预分配的空间难以匹配变化的需求,插入删除操作需要移动大量元素,效率受限。
于是,链表作为一种更加灵活的数据结构被提出。其中,单链表采用 “节点+指针” ——每个节点独立分配内存,通过指针逻辑相连,形成了既可动态扩展又能高效操作的数据链。完美解决了数组的固有限制,支持快速插入删除操作,并能根据需求灵活调整存储规模。本文将深入探讨单链表的C语言实现,系统解析这一重要数据结构的实现原理与应用技巧。
一、单链表的基本结构
#include <stdio.h> // 包含标准输入输出库(用于printf、scanf)
#include <stdlib.h> // 包含内存管理库(用于malloc、free)
//定义单链表节点结构体
typedef struct Node
{
int data; //数据域
struct Node *next; //指针域
}Node,*Link; //给结构体取别名:Node(结点类型)、Link(结点指针类型)
二、头插法
每次创建新节点,都插在“头部”,使链表逆序。
// 头插法创建单链表
Link createListHead(int n) {
Link head = NULL; // 定义头指针,初始时链表为空
Link p; // 用于指向新创建的节点
int x; // 临时存储输入数据
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素:", i + 1);
scanf("%d", &x);
// 创建新节点
p = (Link)malloc(sizeof(Node)); // 动态申请内存
p->data = x; // 给新节点赋值
// 插入到头部
p->next = head;
head = p; //新头指针,让新节点成为新的“头”
}
return head;
}

三、尾插法
每次创建新节点,都插到“尾部”,链表保持正序。
// 尾插法创建单链表
Link createListTail(int n) {
Link head = NULL; // 链表头指针
Link tail = NULL; // 尾指针,用来记录最后一个节点
Link p; // 新节点指针
//上面3条可以简写成:Link head = NULL,tail = NULL,p;
int x;
for (int i = 0; i < n; i++) {
printf("请输入第 %d 个元素:", i + 1);
scanf("%d", &x);
// 创建新节点
p = (Link)malloc(sizeof(Node));
p->data = x;
p->next = NULL; // 新节点默认没有后继节点
// 判断是否是第一个节点
if (head == NULL) {
head = p; // 第一个节点既是头又是尾
tail = p;
} else { //不是第一个,则插入到尾部
tail->next = p; // 旧尾节点的 next 指向新节点
tail = p; // 更新尾指针为新节点
}
}
return head;
}

四、遍历打印
//遍历打印单链表
void printList(Link head) {
Link p = head; // 从头节点开始遍历
while (p != NULL) {
printf("%d -> ", p->data);
p = p->next;
}
printf("NULL\n"); // 末尾指示结束
}
五、删除函数
[先存后删]——分为两种情况:
1.删除的是头节点
2.删除的是中间/结尾节点
// 按值删除结点
Link delete_list(Link head, int key) { //Link head——删哪里, int key——删哪个
Link p = NULL; // p:当前遍历的结点指针
Link q = NULL; // q:p的前驱结点指针(用于“跳过”p)
p = head; // p初始化为头指针,从第一个结点开始遍历
while (1) { // 无限循环,找到目标后用break退出
if (head == NULL) { // 处理空链表场景
printf("链表为空,无结点可删除!\n");
break;
}
// 场景1:头结点就是要删除的结点
if (head->data == key) {
head = head->next; // 头指针指向原头结点的下一个结点
free(p); // 释放原头结点的内存(避免内存泄漏)
printf("已删除数为%d的结点!\n", key);
break; // 找到并删除,退出循环
} else {
q = p; // q跟上p(记录p的前驱)
p = p->next; // p指向下一个结点,继续遍历
if (p == NULL) { // 遍历到末尾未找到
printf("未找到数为%d的结点!\n", key);
break;
}
// 场景2:中间或尾结点是要删除的结点
if (p->data == key) {
q->next = p->next; // 让q的next指向p的下一个结点(跳过p)
free(p); // 释放p的内存
printf("已删除数为%d的结点!\n", key);
break; // 找到并删除,退出循环
}
}
}
return head; // 返回删除后的链表头指针
}

六、插入函数
[先连后断]——在指定值的节点后面插入新节点
// 插入函数
Link insert_List(Link head, Link pnew, int key)
{
Link p = NULL;
p = head;
while(p != NULL) //遍历到末尾停止
{
if(p == NULL){
// 情况1:链表为空或没找到key,插入到头部
pnew->next = head;
head = pnew;
printf("链表为空或未找到%d,新节点插入到头部\n", key);
break;
}
if(p->data == key){
// 情况2:找到key,插入到该节点后面
pnew->next = p->next;
p->next = pnew;
printf("在值为%d的节点后面插入新节点\n", key);
break;
}
p = p->next;
}
return head;
}

七、释放内存
[先存后移]——每次malloc后一定要free,否则会内存泄漏。
// 释放链表内存
void freeList(Link head) {
Link p;
while (head != NULL) {
p = head; // 暂存当前节点
head = head->next; // 移动到下一个节点
free(p); // 释放当前节点
}
}
八、主函数main入口
// 主函数
int main() {
int n,key,choice,value;
Link list1, list2;
printf("请输入链表长度 n: ");
scanf("%d", &n);
printf("\n=== 使用头插法创建链表 ===\n");
list1 = createListHead(n);
printList(list1);
printf("\n=== 使用尾插法创建链表 ===\n");
list2 = createListTail(n);
printList(list2);
// 插入操作演示
printf("\n=== 插入操作演示 ===\n");
// 选择要操作哪个链表
printf("请选择要操作的链表:\n");
printf("1.头插法链表\n");
printf("2.尾插法链表\n");
printf("选择1或2:");
scanf("%d", &choice);
if (choice == 1 || choice == 2) {
// 输入插入信息
printf("请输入要插入的新值:");
scanf("%d", &value);
printf("请输入在哪个值后面插入(如果要插入到头部,输入一个不存在的值):");
scanf("%d", &key);
// 创建新节点
Link newNode = (Link)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL;
if (choice == 1) {
printf("\n插入前链表:");
printList(list1);
list1 = insert_List(list1, newNode, key);
printf("插入后链表:");
printList(list1);
} else {
printf("\n插入前链表:");
printList(list2);
list2 = insert_List(list2, newNode, key);
printf("插入后链表:");
printList(list2);
}
} else {
printf("选择错误!\n");
}
// 删除操作
printf("\n=== 删除操作 ===\n");
// 选择要操作哪个链表
printf("请选择要操作的链表:\n");
printf("1.头插法\n");
printf("2.尾插法\n");
printf("选择1或2:");
scanf("%d", &choice);
// 输入要删除的值
printf("请输入要删除的值:");
scanf("%d", &key);
// 执行删除操作
if (choice == 1) {
printf("删除前链表:");
printList(list1);
list1 = delete_list(list1, key); // 对list1进行删除
printf("删除后链表:");
printList(list1);
}
else if (choice == 2) {
printf("删除前链表:");
printList(list2);
list2 = delete_list(list2, key); // 对list2进行删除
printf("删除后链表:");
printList(list2);
}
else {
printf("选择错误!\n");
}
// 释放内存
freeList(list1);
freeList(list2);
return 0;
}
九、运行结果

注意:理解链表的关键是多多画图!
5万+

被折叠的 条评论
为什么被折叠?



