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

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

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.jsqmd.com/news/283094/

相关文章:

  • 麦橘超然支持seed调节?完整功能实测报告
  • 10分钟完成Qwen儿童图生模型部署:新手入门必看教程
  • 深入解析:linux 安装Kafka 和springboot kaka实战
  • 原型链查找的 O(N) 开销:在超长继承链下属性访问的性能损耗实验 - 详解
  • IndexTTS-2批量合成实战:自动化语音生成部署教程
  • OCR实战应用:用cv_resnet18_ocr-detection提取发票信息全记录
  • 新手必看!YOLOv9官方版镜像从0到推理全流程
  • Emotion2Vec+ Large集群部署:多节点负载均衡方案设计
  • 学生党福音!低成本搭建PyTorch深度学习环境的方法
  • YOLOE镜像使用全解析,一文看懂全部功能组件
  • C#异步与多线程:从入门到实战,避免踩坑的完整指南
  • NotaGen镜像详解:一键生成高质量古典符号化音乐
  • 自动驾驶路牌识别预研:cv_resnet18_ocr-detection初步测试
  • 开放词汇表检测新选择:YOLOE镜像全面测评
  • 实战案例:用fft npainting lama清除广告水印全过程
  • 告别繁琐配置!用科哥镜像快速搭建阿里Paraformer语音识别系统
  • IQuest-Coder-V1如何降低部署门槛?轻量化变体应用指南
  • 杰理之蓝牙发射器发射源选择【篇】
  • 私有化部署+高精度翻译|HY-MT1.5-7B在VuePress中的落地实践
  • MinerU备份策略:模型与数据双重保障机制
  • 杰理之获取蓝牙的ID3歌词和播放时间【篇】
  • 质量好的布袋除尘器供应商哪家便宜?2026年价格分析
  • MinerU是否支持批量OCR?多页PDF处理性能评测
  • 如何用LLM生成高质量古典音乐?NotaGen镜像全解析
  • 如何用GPEN修复童年模糊照?详细步骤来了
  • 杰理之左右声道数据调换【篇】
  • Qwen3-4B-Instruct部署详解:支持多语言生成的配置方法
  • 杰理之APP界面显示异常问题【篇】
  • Python处理中文文件必看(解决utf-8解码错误的4种实战方法)
  • 通义千问3-14B功能测评:119种语言互译真实表现