当前位置: 首页 > news >正文

C语言实现单链表的操作

单链表(Singly Linked List)是一种 ​线性数据结构,由 ​多个节点(Node)组成,每个节点包含:

  1. 数据域(data)​​:存储实际数据(如 intfloat 等)。
  2. 指针域(next)​​:指向下一个节点的地址(最后一个节点的 next 为 NULL)。

1. 单链表的基本结构

​(1) 定义节点结构体

#include <stdio.h>
#include <stdlib.h> // 用于 malloc 和 free// 定义单链表的节点结构
typedef struct Node {int data;           // 数据域(存储数据,这里用 int 举例)struct Node* next;  // 指针域(指向下一个节点)
} Node;
  • data:存储节点的数据(可以是任意类型,如 charfloat、结构体等)。
  • next:指向下一个节点的指针(类型是 struct Node*)。

(2) 定义链表头指针

Node* head = NULL; // 链表头指针(初始为空)
  • head 是整个链表的入口,指向第一个节点(如果链表为空,则 head = NULL)。

2. 单链表的基本操作

​(1) 创建新节点

Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node)); // 分配内存if (newNode == NULL) {printf("内存分配失败!\n");exit(1); // 退出程序}newNode->data = data; // 存储数据newNode->next = NULL; // 新节点默认是最后一个节点,next=NULLreturn newNode;
}
  • 作用​:动态分配内存,创建一个新节点,并初始化 data 和 next
  • ​**malloc**​:用于动态分配内存(需 <stdlib.h>)。
  • ​**sizeof(Node)**​:计算 Node 结构体的大小。

(2) 插入节点(头插法)​

void insertAtHead(int data) {Node* newNode = createNode(data); // 创建新节点newNode->next = head;             // 新节点指向原来的头节点head = newNode;                   // 更新头指针,指向新节点
}
  • 作用​:在链表头部插入新节点(新节点成为新的头节点)。
  • 时间复杂度​:O(1)(直接操作头指针)。

插入 3 → 插入 2 → 插入 1
链表:1 -> 2 -> 3 -> NULL

(3) 插入节点(尾插法)​

void insertAtTail(int data) {Node* newNode = createNode(data); // 创建新节点if (head == NULL) { // 如果链表为空,新节点就是头节点head = newNode;return;}Node* temp = head;while (temp->next != NULL) { // 找到最后一个节点temp = temp->next;}temp->next = newNode; // 最后一个节点的 next 指向新节点
}
  • 作用​:在链表尾部插入新节点(新节点成为最后一个节点)。
  • 时间复杂度​:O(n)(需要遍历到链表末尾)。

插入 1 → 插入 2 → 插入 3
链表:1 -> 2 -> 3 -> NULL

(4) 遍历链表(打印所有节点)​

void printList() {Node* temp = head;while (temp != NULL) { // 遍历直到最后一个节点(next=NULL)printf("%d -> ", temp->data);temp = temp->next;}printf("NULL\n");
}
  • 作用​:从头节点开始,依次打印每个节点的数据,直到 NULL

1 -> 2 -> 3 -> NULL

(5) 删除节点(删除头节点)​

void deleteAtHead() {if (head == NULL) {printf("链表为空,无法删除!\n");return;}Node* temp = head;    // 保存当前头节点head = head->next;    // 头指针指向下一个节点free(temp);           // 释放原头节点的内存
}
  • 作用​:删除链表的第一个节点(头节点)。
  • 时间复杂度​:O(1)

(6) 释放整个链表(防止内存泄漏)​

void freeList() {Node* temp;while (head != NULL) {temp = head;      // 保存当前节点head = head->next; // 头指针指向下一个节点free(temp);       // 释放当前节点}printf("链表已释放!\n");
}
  • 作用​:遍历链表,逐个释放所有节点的内存,防止内存泄漏。
  • 必须手动释放​(C语言没有自动垃圾回收)。

(7) 单链表指定位置插入节点

在单链表中实现 ​指定位置插入节点​ 是常见操作,核心是找到插入位置的 ​前驱节点,然后调整指针完成插入。

指定位置插入通常有两种场景:

  • 场景 1​:在第 k 个节点 ​之前​ 插入新节点(例如,在第 3 个节点前插入,新节点成为第 3 个节点)。
  • 场景 2​:在第 k 个节点 ​之后​ 插入新节点(例如,在第 3 个节点后插入,新节点成为第 4 个节点)。

为了方便判断 k 是否有效,先实现一个计算链表长度的函数getLength:

int getLength() {int len = 0;Node* temp = head;while (temp != NULL) {len++;temp = temp->next;}return len;
}
(7-1) 指定位置插入函数(第 k 个节点前插入)​
// 在第 k 个节点前插入新节点(k 从 1 开始计数)
// 返回值:成功返回 0,失败返回 -1(如 k 无效)
int insertAtPosition(int data, int k) {// 情况 1:链表为空,仅当 k=1 有效if (head == NULL) {if (k == 1) {insertAtHead(data); // 头插法return 0;} else {printf("插入失败:链表为空,k 必须为 1\n");return -1;}}// 情况 2:k=1(插入为新的头节点)if (k == 1) {insertAtHead(data);return 0;}// 情况 3:k > 链表长度(插入到末尾)int len = getLength();if (k > len) {printf("插入失败:k=%d 超过链表长度 %d,插入到末尾\n", k, len);insertAtTail(data); // 尾插法return 0;}// 情况 4:正常插入(找到第 k-1 个节点)Node* prev = head;for (int i = 1; i < k - 1; i++) { // 遍历到第 k-1 个节点prev = prev->next;}// 创建新节点Node* newNode = createNode(data);if (newNode == NULL) {printf("内存分配失败!\n");return -1;}// 插入新节点newNode->next = prev->next; // 新节点指向原第 k 个节点prev->next = newNode;       // 第 k-1 个节点指向新节点return 0;
}
代码说明
​(1) 边界条件处理
  • 链表为空​:仅允许 k=1(插入为头节点),否则报错。
  • k=1​:直接调用头插法(insertAtHead)。
  • k 超过链表长度​:插入到末尾(调用尾插法 insertAtTail)。
​(2) 找到前驱节点

通过循环遍历 k-1 次,找到第 k-1 个节点(prev)。例如:

  • 若 k=3,需要找到第 2 个节点(prev),新节点插入到 prev 之后。
​(3) 插入逻辑
  • 新节点的 next 指向 prev->next(原第 k 个节点)。
  • prev->next 指向新节点,完成插入。
(4)测试代码
int main() {// 初始化链表为空Node* head = NULL;// 测试 1:插入到 k=1(链表为空)insertAtPosition(10, 1); // 链表:10 -> NULLprintList();// 测试 2:插入到 k=2(链表长度 1,k=2 超过长度,插入到末尾)insertAtPosition(20, 2); // 链表:10 -> 20 -> NULLprintList();// 测试 3:插入到 k=2(正常插入)insertAtPosition(15, 2); // 链表:10 -> 15 -> 20 -> NULLprintList();// 测试 4:插入到 k=4(链表长度 3,k=4 超过长度,插入到末尾)insertAtPosition(25, 4); // 链表:10 -> 15 -> 20 -> 25 -> NULLprintList();// 释放链表freeList();return 0;
}
插入失败:链表为空,k 必须为 1 (注:实际代码中 k=1 会成功,此处为示例错误提示调整)
10 -> NULL
插入失败:k=2 超过链表长度 1,插入到末尾
10 -> 20 -> NULL
10 -> 15 -> 20 -> NULL
插入失败:k=4 超过链表长度 3,插入到末尾
10 -> 15 -> 20 -> 25 -> NULL
链表已释放!
(7-2) 指定位置插入函数(第 k 个节点后插入)​

若需要在第 k 个节点 ​之后​ 插入新节点

// 在第 k 个节点后插入新节点(k 从 1 开始计数)
int insertAfterPosition(int data, int k) {// 处理链表为空的情况if (head == NULL) {if (k == 0) { // 特殊处理:k=0 表示插入到空链表insertAtHead(data);return 0;} else {printf("插入失败:链表为空,k 必须为 0\n");return -1;}}// 找到第 k 个节点Node* curr = head;for (int i = 1; i < k; i++) {curr = curr->next;if (curr == NULL) { // k 超过链表长度printf("插入失败:k=%d 超过链表长度\n", k);return -1;}}// 创建新节点并插入Node* newNode = createNode(data);newNode->next = curr->next;curr->next = newNode;return 0;
}

3. 总结

操作关键步骤时间复杂度
指定位置前插入找到第 k-1 个节点 → 调整指针O(n)(最坏情况遍历整个链表)
指定位置后插入找到第 k 个节点 → 调整指针O(n)(最坏情况遍历整个链表)

4. 完整代码示例

#include <stdio.h>
#include <stdlib.h>// 定义单链表节点
typedef struct Node {int data;struct Node* next;
} Node;Node* head = NULL; // 链表头指针// 创建新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");exit(1);}newNode->data = data;newNode->next = NULL;return newNode;
}// 头插法插入节点
void insertAtHead(int data) {Node* newNode = createNode(data);newNode->next = head;head = newNode;
}// 尾插法插入节点
void insertAtTail(int data) {Node* newNode = createNode(data);if (head == NULL) {head = newNode;return;}Node* temp = head;while (temp->next != NULL) {temp = temp->next;}temp->next = newNode;
}// 打印链表
void printList() {Node* temp = head;while (temp != NULL) {printf("%d -> ", temp->data);temp = temp->next;}printf("NULL\n");
}// 删除头节点
void deleteAtHead() {if (head == NULL) {printf("链表为空,无法删除!\n");return;}Node* temp = head;head = head->next;free(temp);
}// 释放整个链表
void freeList() {Node* temp;while (head != NULL) {temp = head;head = head->next;free(temp);}printf("链表已释放!\n");
}int main() {// 测试代码insertAtHead(3); // 3 -> NULLinsertAtHead(2); // 2 -> 3 -> NULLinsertAtHead(1); // 1 -> 2 -> 3 -> NULLprintList();     // 输出:1 -> 2 -> 3 -> NULLinsertAtTail(4); // 1 -> 2 -> 3 -> 4 -> NULLprintList();     // 输出:1 -> 2 -> 3 -> 4 -> NULLdeleteAtHead();  // 删除 1,剩下 2 -> 3 -> 4 -> NULLprintList();     // 输出:2 -> 3 -> 4 -> NULLfreeList();      // 释放链表return 0;
}

5. 单链表的特点

✅ ​优点​:

  • 动态大小​:不需要预先分配固定内存,可以灵活增减节点。
  • 插入/删除高效​(特别是头插法 O(1))。

❌ ​缺点​:

  • 查找慢​(必须从头遍历,查找某个节点需要 O(n))。
  • 额外空间​:每个节点需要存储 next 指针,占用额外内存。
http://www.dtcms.com/a/315052.html

相关文章:

  • 机器学习(11):岭回归Ridge
  • 不损失清晰度情况对图片进行压缩的工具类(可通过地址也可以通过文件调用)
  • 基于实时音视频技术的远程控制传输SDK的功能设计
  • 基于特征融合的医学图像分类算法
  • #C语言——刷题攻略:牛客编程入门训练(四):运算(二)
  • 【基于超表面实现电磁感应透明(EIT)的复现与讲解】
  • Spring P1 | 创建你的第一个Spring MVC项目(IDEA图文详解版,社区版专业版都有~)
  • [Shell编程] 零基础入门 Shell 编程:从概念到第一个脚本
  • 基于TurboID的邻近标记质谱(PL-MS)实验指南:从质粒构建到质谱鉴定
  • 【OS】操作系统概述
  • 互联网医院整体项目套表整理过程文档全流程分析
  • Stanford CS336 assignment1 | Byte-Pair Encoding (BPE) Tokenizer
  • 飞算JavaAI:颠覆传统开发的智能利器
  • Effective C++ 条款22: 将成员变量声明为private
  • Pixel 4D 3.4.4.0 | 支持丰富的壁纸资源,高清画质,高度的个性化设置能力,智能推荐功能
  • Ubuntu 下 MySQL 离线部署教学(含手动步骤与一键脚本)
  • 力扣面试150题--加一
  • ZCC1004E-120V 3A 零功耗使能异步降压电源芯片
  • 人工智能之数学基础:条件概率及其应用
  • JS中的Set和WeakSet联系与区别
  • 数据结构---配置网络步骤、单向链表额外应用
  • 【Linux】Linux 操作系统 - 33 , 线程(二) 线程互斥和同步 , 带你对线程使用深刻理解 !
  • 《Python 实用项目与工具制作指南》· 2.2 变量
  • JVM调优工具详解
  • 把“距离过近”的节点(或端点)合并成一个,避免重复。机器学习 python
  • web:ts元组
  • 【RH124知识点问答题】第8章 监控和管理 Linux 进程
  • Bean的生命周期和循环依赖问题的解决
  • 防火墙认证用户部署
  • 开发规范(一)移动端