C语言-单向循环链表不带头节点的基本操作(增、删、改、查)

news/2026/1/22 9:02:42/文章来源:https://www.cnblogs.com/hex0110/p/19515008

C语言-单向循环链表不带头节点的基本操作(增、删、改、查)

前言

这篇博客将带你从零开始,逐步实现一个不带头节点的单向循环链表,并完成其创建、遍历、增、删、改、查等核心操作。我们将重点关注那些容易出错的边界条件,并用清晰的代码进行解析。

详细代码

1、所需要包含的头文件以及定义链表的节点结构

#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{int data;struct Node* next;
} Node;

2、创建新节点

Node* createNode(int data)
{Node* newNode = (Node*)malloc(sizeof(Node));if (!newNode){printf("内存分配失败\n");exit(1);}newNode->next = NULL;newNode->data = data;return newNode;
}

原理解释:创建的新节点的next先指向NULL

3、初始化空链表

Node* initList()
{return NULL;
}

4、判断链表是否为空

int isEmpty(Node* head)
{return head == NULL;
}

5、头插法

Node* insertAtHead(Node* head, int data)
{Node* newNode = createNode(data);//空链表,新节点指向自己if (isEmpty(head)){newNode->next = newNode;return newNode;}//非空链表,找到尾节点,插入到头部Node* tail = head;while (tail->next != head){tail = tail->next;}newNode->next = head;tail->next = newNode;return newNode;
}

原理解释:这里分两种情况讨论。第一种如果链表为空,则新节点指向自己;第二种如果链表非空,则找到尾部节点,然后插入到头部

6、尾插法

Node* insertAtTail(Node* head, int data)
{Node* newNode = createNode(data);//空链表,新节点指向自己if (isEmpty(head)){newNode->next = newNode;return newNode;}//非空链表,找到尾节点,插入到尾部Node* tail = head;while (tail->next != head){tail = tail->next;}tail->next = newNode;newNode->next = head;return head;
}

原理解释:这里分两种情况讨论。第一种如果链表为空,则新节点指向自己;第二种如果链表非空,则找到尾部节点,然后插入到尾部

7、在指定位置插入节点(位置从0开始)

Node* insertAtPosition(Node* head, int data, int position)
{if (position < 0){printf("位置不能为负的\n");return head;}//如果位置为0,相当于头插if (position == 0){return insertAtHead(head, data);}//空链表但位置不为0if (isEmpty(head)){printf("链表为空,但位置%d不为0,将插入到位置0\n", position);return insertAtHead(head, data);}Node* newNode = createNode(data);Node* current = head;int index = 0;//找到要插入位置的前一个节点while (current->next != head && index < position - 1){current = current->next;index++;}//如果位置超过链表长度,插入到末尾if (index < position - 1){printf("位置%d超出链表长度,将插入到末尾\n", position);}newNode->next = current->next;current->next = newNode;return head;
}

原理解释:
首先我们要先判断3种特殊情况。第一,插入的位置小于0,这是不被允许的,代码不进行任何插入操作,直接返回链表的头部节点;第二,插入的位置等于0,相当于头插,直接调用上面的函数即可;第三,链表为空但位置不为0,也是相当于头插,调用头插函数即可;
接着我们找到要插入位置的前一个节点,顺带判断一下位置是否超过链表长度,若超过,则直接插入到末尾。
最后正常插入即可。

8、在指定值后插入节点

Node* insertAfterValue(Node* head, int targetValue, int newValue)
{if (isEmpty(head)){printf("链表为空,无法插入\n");return head;}Node* current = head;//查找目标值do{if (current->data == targetValue){Node* newNode = createNode(newValue);newNode->next = current->next;current->next = newNode;return head;}current = current->next;} while (current != head);printf("未找到值为%d的节点\n", targetValue);return head;
}

原理解释:查找目标值这里选择do-while是因为,不管while有没有成立,都会至少执行一次do里面的代码

9、删除指定值的节点

Node* deleteByValue(Node* head, int data)
{if (isEmpty(head)){printf("链表为空\n");return NULL;}//如果链表只有一个节点if (head->next == head){if (head->data == data){free(head);return NULL;}else{printf("未找到数据为%d的节点\n", data);return head;}}Node* current = head;Node* prev = NULL;//查找要删除的节点do{if (current->data == data){break;}prev = current;current = current->next;} while (current != head);//未找到if (current->data != data){printf("未找到数据为%d的节点\n", data);return head;}//如果要删除的是头节点if (current == head){//找到尾节点Node* tail = head;while (tail->next != head){tail = tail->next;}tail->next = head->next;Node* newHead = head->next;free(head);return newHead;}//删除中间或尾部节点prev->next = current->next;free(current);return head;
}

原理解释:依旧先处理链表为空以及链表只有一个节点的特殊情况。然后进行正常的查找删除操作。这里不管删除的是中间节点还是尾部节点都满足"前驱指向后继"的逻辑,所以可以共用同一条语句。

10、查找节点

Node* search(Node* head, int data)
{if (isEmpty(head))return NULL;Node* current = head;do{if (current->data == data)return current;current = current->next;} while (current != head);return NULL;
}

11、获取链表长度

int getLength(Node* head)
{if (isEmpty(head))return 0;int length = 0;Node* current = head;do{length++;current = current->next;} while (current != head);return length;
}

12、打印链表

void printList(Node* head)
{if (isEmpty(head)){printf("链表为空\n");return;}Node* current = head;printf("链表内容: ");do{printf("%d", current->data);current = current->next;if (current != head)printf(" -> ");} while (current != head);printf(" -> ...(循环回到头部)\n");
}

13、销毁链表

void destroyList(Node* head)
{if (isEmpty(head))return;Node* current = head->next;Node* nextNode = NULL;//释放除头节点外的所有节点while (current != head){nextNode = current->next;free(current);current = nextNode;}free(head);
}

14、测试函数

int main() {Node* list = NULL;printf("=== 不带头节点的单向循环链表测试 ===\n\n");// 测试空链表printf("1. 初始化空链表:\n");printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试尾插法printf("2. 尾插法插入 1, 2, 3:\n");list = insertAtTail(list, 1);list = insertAtTail(list, 2);list = insertAtTail(list, 3);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试头插法printf("3. 头插法插入 0:\n");list = insertAtHead(list, 0);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试指定位置插入printf("4. 在位置2插入 99:\n");list = insertAtPosition(list, 99, 2);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试在指定值后插入printf("5. 在值2后插入 88:\n");list = insertAfterValue(list, 2, 88);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试边界情况:位置超出printf("6. 在位置10插入 77:\n");list = insertAtPosition(list, 77, 10);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试查找printf("7. 查找值为2的节点:\n");Node* found = search(list, 2);if (found) {printf("找到节点,值为: %d\n\n", found->data);}else {printf("未找到节点\n\n");}// 测试删除printf("8. 删除值为99的节点:\n");list = deleteByValue(list, 99);printList(list);printf("链表长度: %d\n\n", getLength(list));// 测试删除头节点printf("9. 删除头节点(值为0):\n");list = deleteByValue(list, 0);printList(list);printf("链表长度: %d\n\n", getLength(list));// 销毁链表printf("10. 销毁链表:\n");destroyList(list);list = NULL;printList(list);return 0;
}

应用场景

  1. 固定内存块管理(内存池)
  2. 轮询调度(任务/定时器)
  3. 循环缓冲区
  4. 嵌入式资源管理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1199177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

麦橘超然支持seed调节?完整功能实测报告

麦橘超然支持seed调节&#xff1f;完整功能实测报告 1. 引言&#xff1a;本地AI绘画的新选择——麦橘超然控制台 你有没有遇到过这种情况&#xff1a;想用AI画一张特定风格的图&#xff0c;结果每次生成都“随机发挥”&#xff0c;根本没法复现上次那个惊艳的效果&#xff1f…

10分钟完成Qwen儿童图生模型部署:新手入门必看教程

10分钟完成Qwen儿童图生模型部署&#xff1a;新手入门必看教程 你是否想为孩子生成一张可爱的动物图片&#xff0c;却苦于不会画画&#xff1f;或者想找一个简单易用的AI工具&#xff0c;让孩子在安全、有趣的环境中接触人工智能&#xff1f;本文将带你10分钟内完成Qwen儿童图…

YOLOv13目标检测太简单:一行命令搞定预测

YOLOv13目标检测太简单&#xff1a;一行命令搞定预测 你是否还在为配置目标检测环境而头疼&#xff1f;下载依赖、编译源码、调试CUDA版本……这些繁琐的步骤不仅耗时&#xff0c;还容易出错。更别提当团队协作时&#xff0c;每个人的机器环境不一致&#xff0c;导致“在我电脑…

深入解析:linux 安装Kafka 和springboot kaka实战

深入解析:linux 安装Kafka 和springboot kaka实战pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

DeepSeek-R1-Distill-Qwen-1.5B自动化测试:API稳定性验证方案

DeepSeek-R1-Distill-Qwen-1.5B自动化测试&#xff1a;API稳定性验证方案 1. 引言&#xff1a;为什么我们需要API稳定性验证&#xff1f; 你有没有遇到过这种情况&#xff1a;模型服务明明部署好了&#xff0c;接口也能调通&#xff0c;但跑着跑着突然响应变慢、返回乱码&…

原型链查找的 O(N) 开销:在超长继承链下属性访问的性能损耗实验 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

YOLOv13镜像实战:快速构建校园安全监控Demo

YOLOv13镜像实战&#xff1a;快速构建校园安全监控Demo 在智慧校园建设不断推进的今天&#xff0c;如何利用AI技术提升校园安全管理效率&#xff0c;成为教育机构关注的重点。传统监控系统依赖人工回看录像&#xff0c;不仅耗时耗力&#xff0c;还容易遗漏关键事件。而基于目标…

IndexTTS-2批量合成实战:自动化语音生成部署教程

IndexTTS-2批量合成实战&#xff1a;自动化语音生成部署教程 Sambert 多情感中文语音合成——开箱即用版。本镜像基于阿里达摩院 Sambert-HiFiGAN 模型&#xff0c;已深度修复 ttsfrd 二进制依赖及 SciPy 接口兼容性问题。内置 Python 3.10 环境&#xff0c;支持知北、知雁等多…

OCR实战应用:用cv_resnet18_ocr-detection提取发票信息全记录

OCR实战应用&#xff1a;用cv_resnet18_ocr-detection提取发票信息全记录 1. 为什么选择cv_resnet18_ocr-detection做发票识别&#xff1f; 在财务自动化和企业数字化转型中&#xff0c;发票信息提取是高频刚需场景。每天成百上千张增值税专用发票、普通发票、电子发票需要人…

2026年水泥假山建造优质服务商推荐榜

2026年水泥假山建造优质服务商推荐榜一、行业背景与筛选维度《2025-2030年中国文旅景观行业发展白皮书》数据显示,乡村振兴及文旅项目中,假山景观作为民宿核心配套设施,可提升项目客流转化率32%,带动民宿入住率提升…

新手必看!YOLOv9官方版镜像从0到推理全流程

新手必看&#xff01;YOLOv9官方版镜像从0到推理全流程 你是不是也经历过这样的场景&#xff1a;好不容易下定决心要动手跑一个目标检测模型&#xff0c;结果光是配置环境就花了大半天&#xff1f;PyTorch版本不对、CUDA不兼容、依赖包冲突……这些问题让很多刚入门的同学望而…

热门的波纹式脱硝催化剂品牌2026年哪家质量好?深度测评

在2026年环保行业快速发展的背景下,选择优质的波纹式脱硝催化剂对企业实现超低排放至关重要。本文基于产品性能、技术创新、市场反馈及服务能力等核心指标,对当前市场上表现突出的品牌进行深度测评。经过全面评估,山…

Emotion2Vec+ Large集群部署:多节点负载均衡方案设计

Emotion2Vec Large集群部署&#xff1a;多节点负载均衡方案设计 1. 引言&#xff1a;为什么需要集群化部署&#xff1f; Emotion2Vec Large 是一个高性能的语音情感识别模型&#xff0c;具备强大的特征提取能力与高精度的情感分类表现。然而&#xff0c;单机部署在面对高并发…

学生党福音!低成本搭建PyTorch深度学习环境的方法

学生党福音&#xff01;低成本搭建PyTorch深度学习环境的方法 1. 为什么学生更需要“开箱即用”的AI开发环境&#xff1f; 对于大多数学生来说&#xff0c;搞深度学习最头疼的不是模型不会调&#xff0c;而是环境装不上。明明代码写得没问题&#xff0c;一运行就报错&#xf…

YOLOE镜像使用全解析,一文看懂全部功能组件

YOLOE镜像使用全解析&#xff0c;一文看懂全部功能组件 你是否试过在深夜调试目标检测模型&#xff0c;却卡在环境配置上&#xff1f;下载权重、编译CUDA扩展、解决torch版本冲突……还没开始推理&#xff0c;GPU显存就先被报错占满。更别提开放词汇检测这种新范式——传统YOL…

C#异步与多线程:从入门到实战,避免踩坑的完整指南

本文深入探讨了C#异步与多线程编程的核心概念、发展历程及实战应用。从早期APM/EAP模式到现代async/await范式,系统解析了异步编程的原理与常见误区。通过丰富的代码示例,展示了如何避免UI卡顿、实现并发控制、处理异…

自动驾驶路牌识别预研:cv_resnet18_ocr-detection初步测试

自动驾驶路牌识别预研&#xff1a;cv_resnet18_ocr-detection初步测试 在自动驾驶系统的感知模块中&#xff0c;交通标志与文字信息的准确识别是实现环境理解的重要一环。尤其是在城市复杂道路场景下&#xff0c;路牌上的限速、禁行、方向指引等文本内容对决策系统具有直接指导…

NotaGen镜像详解:一键生成高质量古典符号化音乐

NotaGen镜像详解&#xff1a;一键生成高质量古典符号化音乐 1. 快速上手NotaGen音乐生成系统 你是否曾幻想过&#xff0c;只需轻点几下鼠标&#xff0c;就能创作出一段优雅的巴赫风格赋格&#xff0c;或是充满浪漫主义气息的肖邦夜曲&#xff1f;现在&#xff0c;这一切不再是…

实战案例:用fft npainting lama清除广告水印全过程

实战案例&#xff1a;用fft npainting lama清除广告水印全过程 1. 引言&#xff1a;为什么需要高效去水印工具&#xff1f; 你有没有遇到过这种情况&#xff1f;好不容易找到一张满意的图片&#xff0c;结果上面却盖着醒目的广告水印。手动修图费时费力&#xff0c;PS技术门槛…

开放词汇表检测新选择:YOLOE镜像全面测评

开放词汇表检测新选择&#xff1a;YOLOE镜像全面测评 在智能安防监控中心的大屏前&#xff0c;值班人员正通过AI系统实时分析数十路摄像头画面。突然&#xff0c;一个从未在训练集中出现过的新型无人机出现在视野中——传统目标检测模型对此类“未知物体”往往束手无策&#x…