数据结构——单向循环链表

文章目录

1. 概念

2. 区别

2.1 结构区别

2.2 访问方式区别

2.3 优缺点对比

3. 流程

4. 基本操作

5. 代码示例


1. 概念

单向循环链表是一种特殊的单链表,其中最后一个节点的后继指针指向头节点,形成一个环。单向循环链表适合用于需要循环访问数据的场景,如约瑟夫环问题。

节点定义

每个节点包含两个部分:数据域和指针域。

typedef struct Node {int data;           // 数据域,存储节点的值struct Node *next;  // 指针域,指向下一个节点
} Node;

结构定义

单向循环链表包含一个头指针和链表中的节点个数。

typedef struct {Node *head; // 指向单向循环链表的头节点size_t size; // 单向循环链表中的节点个数
} CircularLinkedList;

2. 区别

单向链表和单向循环链表的区别

2.1 结构区别

单向链表(Singly Linked List)

  • 每个节点包含一个数据域和一个指向下一个节点的指针。
  • 最后一个节点的指针指向 NULL,表示链表的结束。
  • 只能单向遍历,从头节点开始到尾节点结束。

单向循环链表(Singly Circular Linked List)

  • 每个节点也包含一个数据域和一个指向下一个节点的指针。
  • 最后一个节点的指针指向头节点,形成一个循环。
  • 可以从链表中的任何一个节点开始遍历,最终会回到该节点。

2.2 访问方式区别

单向链表

  • 从头节点开始遍历,直到找到目标节点或者到达链表末尾。

单向循环链表

  • 从任意节点开始遍历,最终会回到该节点,因此在循环中进行操作时更加方便。

2.3 优缺点对比

单向链表的优缺点

优点

  1. 实现简单:实现和维护相对简单,每个节点只包含一个指针。
  2. 内存开销小:每个节点只包含一个指针,内存占用较少。
  3. 插入和删除操作效率较高:在已知前驱节点的情况下,插入和删除节点的时间复杂度为 O(1)。

缺点

  1. 只能单向遍历:不能从尾节点向前遍历到头节点,灵活性差。
  2. 查找效率较低:从头节点遍历到目标节点的时间复杂度为 O(n)。

单向循环链表的优缺点

优点

  1. 循环访问方便:适合需要循环访问数据的应用场景,如约瑟夫环问题。
  2. 无需处理链表末尾:由于最后一个节点指向头节点,不需要特殊处理链表末尾的情况。

缺点

  1. 实现和维护复杂:插入和删除操作需要特别注意尾节点和头节点之间的关系。
  2. 查找效率较低:和单向链表一样,需要遍历链表才能找到特定节点,时间复杂度为 O(n)。

3. 流程

初始化单向循环链表

  • 设置头指针为NULL。
  • 设置节点个数为0。

插入新节点

  • 判断插入位置是否是0。
  • 如果是0,进一步判断链表是否为空:
    • 如果链表为空,新节点指向自己,头指针指向新节点。
    • 如果链表不为空,找到尾节点,新节点指向头节点,头指针指向新节点,尾节点指向新节点。
  • 如果插入位置不是0,找到插入位置的前一个节点,新节点指向前一个节点的后继节点,前一个节点指向新节点。
  • 节点个数加1。

删除节点

  • 判断删除位置是否是0。
  • 如果是0,保存头节点,进一步判断链表是否只有一个节点:
    • 如果链表只有一个节点,头指针置为NULL。
    • 如果链表有多个节点,找到尾节点,头指针指向头节点的后继节点,尾节点指向新头节点。
  • 如果删除位置不是0,找到删除位置前一个节点,保存要删除的节点,前一个节点指向要删除节点的后继节点。
  • 释放被删除的节点,节点个数减1。

获取指定位置的元素

  • 判断索引是否合法,如果不合法返回无效索引。
  • 从头节点开始遍历,遍历到目标位置节点,返回节点数据。

修改指定位置的元素

  • 判断索引是否合法,如果不合法忽略位置。
  • 从头节点开始遍历,遍历到目标位置节点,修改节点数据。

释放单向循环链表内存

  • 判断链表是否为空,如果为空直接返回。
  • 从头节点开始遍历,保存下一个节点,释放当前节点,继续遍历,直到回到头节点。
  • 头指针置为NULL,节点个数置为0。

 

4. 基本操作

初始化单向循环链表

void initCircularLinkedList(CircularLinkedList *list) {list->head = NULL; // 初始化头节点为空list->size = 0;    // 初始化节点个数为0
}
  • 功能:初始化一个空的单向循环链表。
  • 参数CircularLinkedList *list,指向需要初始化的链表结构。
  • 操作
    • 将链表的头节点指针 head 设置为 NULL
    • 将链表的节点个数 size 设置为 0

 

插入节点

在单向循环链表的指定位置插入新节点。

void insertAt(CircularLinkedList *list, size_t index, int element) {if (index > list->size) {return; // 忽略无效的插入位置}Node *newNode = (Node *)malloc(sizeof(Node)); // 创建新节点newNode->data = element;if (index == 0) { // 插入位置是头节点if (list->head == NULL) { // 如果链表为空newNode->next = newNode;list->head = newNode;} else {Node *tail = list->head;while (tail->next != list->head) {tail = tail->next;}newNode->next = list->head;list->head = newNode;tail->next = newNode;}} else {Node *prevNode = list->head;for (size_t i = 0; i < index - 1; i++) {prevNode = prevNode->next;}newNode->next = prevNode->next;prevNode->next = newNode;}list->size++; // 更新节点个数
}
  • 功能:在指定位置插入新节点。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,插入位置的索引。
    • int element,插入节点的值。
  • 操作
    • 检查插入位置是否合法,如果不合法则直接返回。
    • 创建一个新节点并赋值。
    • 如果插入位置是头节点:
      • 如果链表为空,直接将新节点的 next 指针指向自己,并将 head 指针指向新节点。
      • 如果链表不为空,找到尾节点,更新尾节点和新节点的指针,使新节点成为头节点。
    • 如果插入位置不是头节点,找到插入位置的前一个节点,更新前一个节点和新节点的指针,使新节点插入到指定位置。
    • 更新链表的节点个数。

 

删除节点

删除指定位置的节点并返回被删除的元素。

int deleteAt(CircularLinkedList *list, size_t index) {if (index >= list->size) {return -1; // 忽略无效的删除位置}int deletedElement;if (index == 0) { // 删除位置是头节点Node *temp = list->head;if (list->head->next == list->head) { // 如果链表只有一个节点list->head = NULL;} else {Node *tail = list->head;while (tail->next != list->head) {tail = tail->next;}list->head = temp->next;tail->next = list->head;}deletedElement = temp->data;free(temp); // 释放被删除节点的内存} else {Node *prevNode = list->head;for (size_t i = 0; i < index - 1; i++) {prevNode = prevNode->next;}Node *temp = prevNode->next;prevNode->next = temp->next;deletedElement = temp->data;free(temp); // 释放被删除节点的内存}list->size--; // 更新节点个数return deletedElement;
}
  • 功能:删除指定位置的节点并返回被删除的节点的值。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,删除位置的索引。
  • 操作
    • 检查删除位置是否合法,如果不合法则返回 -1
    • 如果删除位置是头节点:
      • 如果链表只有一个节点,将 head 指针置为 NULL
      • 如果链表有多个节点,找到尾节点,更新尾节点和头节点的指针,使头节点指向下一个节点。
      • 释放被删除节点的内存,返回被删除节点的值。
    • 如果删除位置不是头节点,找到删除位置的前一个节点,更新前一个节点和下一个节点的指针,删除指定位置的节点。
    • 更新链表的节点个数,返回被删除节点的值。

获取节点

// 获取指定位置的元素
int getElementAt(const CircularLinkedList *list, size_t index) {if (index >= list->size) {return -1; // 返回无效的索引}Node *currentNode = list->head;for (size_t i = 0; i < index; i++) {currentNode = currentNode->next;}return currentNode->data; // 返回指定位置的元素
}
  • 功能:获取指定位置的节点值。
  • 参数
    • const CircularLinkedList *list,指向链表结构。
    • size_t index,目标位置的索引。
  • 操作
    • 检查索引是否合法,如果不合法则返回 -1
    • 从头节点开始遍历,直到找到目标位置的节点。
    • 返回目标位置节点的值。

修改节点

// 修改指定位置的元素
void modifyAt(CircularLinkedList *list, size_t index, int newValue) {if (index >= list->size) {return; // 忽略无效的修改位置}Node *currentNode = list->head;for (size_t i = 0; i < index; i++) {currentNode = currentNode->next;}currentNode->data = newValue; // 修改节点的值
}
  • 功能:修改指定位置的节点值。
  • 参数
    • CircularLinkedList *list,指向链表结构。
    • size_t index,目标位置的索引。
    • int newValue,新值。
  • 操作
    • 检查索引是否合法,如果不合法则直接返回。
    • 从头节点开始遍历,直到找到目标位置的节点。
    • 修改目标位置节点的值。

打印所有元素

void printCircularLinkedList(const CircularLinkedList *list) {if (list->head == NULL) {printf("链表为空\n");return;}Node *currentNode = list->head;do {printf("%d ", currentNode->data);currentNode = currentNode->next;} while (currentNode != list->head);printf("\n");
}
  • 功能:打印单向循环链表中的所有节点值。
  • 参数const CircularLinkedList *list,指向需要打印的链表结构。
  • 操作
    • 如果链表为空,打印“链表为空”并返回。
    • 从头节点开始遍历,每次打印当前节点的值。
    • 继续遍历,直到回到头节点。

 

释放单向循环链表的内存

void destroyCircularLinkedList(CircularLinkedList *list) {if (list->head == NULL) {return;}Node *currentNode = list->head;Node *nextNode;do {nextNode = currentNode->next;free(currentNode);currentNode = nextNode;} while (currentNode != list->head);list->head = NULL;list->size = 0;
}
  • 功能:释放单向循环链表中所有节点的内存。
  • 参数CircularLinkedList *list,指向需要释放的链表结构。
  • 操作
    • 如果链表为空,直接返回。
    • 从头节点开始遍历,每次保存当前节点的下一个节点的指针,释放当前节点的内存。
    • 继续遍历,直到回到头节点。
    • 将链表的头节点指针 head 置为 NULL,将链表的节点个数 size 置为 0

5. 代码示例

以下是一个完整的单向循环链表实现代码,包括初始化、插入、删除、获取和修改元素,以及释放链表的内存的所有基本操作:

#include <stdio.h>
#include <stdlib.h>// 单向循环链表节点结构定义
typedef struct Node {int data;           // 节点存储的数据struct Node *next;  // 指向下一个节点的指针
} Node;// 单向循环链表结构定义
typedef struct {Node *head;         // 单向循环链表头节点指针size_t size;        // 单向循环链表中的节点个数
} CircularLinkedList;// 初始化单向循环链表
void initCircularLinkedList(CircularLinkedList *list) {list->head = NULL; // 初始化头节点为空list->size = 0;    // 初始化节点个数为0
}// 在指定位置插入元素
void insertAt(CircularLinkedList *list, size_t index, int element) {if (index > list->size) {return; // 忽略无效的插入位置}Node *newNode = (Node *)malloc(sizeof(Node)); // 创建新节点newNode->data = element;if (index == 0) { // 插入位置是头节点if (list->head == NULL) { // 如果链表为空newNode->next = newNode;list->head = newNode;} else {Node *tail = list->head;while (tail->next != list->head) {tail = tail->next;}newNode->next = list->head;list->head = newNode;tail->next = newNode;}} else {Node *prevNode = list->head;for (size_t i = 0; i < index - 1; i++) {prevNode = prevNode->next;}newNode->next = prevNode->next;prevNode->next = newNode;}list->size++; // 更新节点个数
}// 删除指定位置的元素并返回被删除的元素
int deleteAt(CircularLinkedList *list, size_t index) {if (index >= list->size) {return -1; // 忽略无效的删除位置}int deletedElement;if (index == 0) { // 删除位置是头节点Node *temp = list->head;if (list->head->next == list->head) { // 如果链表只有一个节点list->head = NULL;} else {Node *tail = list->head;while (tail->next != list->head) {tail = tail->next;}list->head = temp->next;tail->next = list->head;}deletedElement = temp->data;free(temp); // 释放被删除节点的内存} else {Node *prevNode = list->head;for (size_t i = 0; i < index - 1; i++) {prevNode = prevNode->next;}Node *temp = prevNode->next;prevNode->next = temp->next;deletedElement = temp->data;free(temp); // 释放被删除节点的内存}list->size--; // 更新节点个数return deletedElement;
}// 获取指定位置的元素
int getElementAt(const CircularLinkedList *list, size_t index) {if (index >= list->size) {return -1; // 返回无效的索引}Node *currentNode = list->head;for (size_t i = 0; i < index; i++) {currentNode = currentNode->next;}return currentNode->data; // 返回指定位置的元素
}// 修改指定位置的元素
void modifyAt(CircularLinkedList *list, size_t index, int newValue) {if (index >= list->size) {return; // 忽略无效的修改位置}Node *currentNode = list->head;for (size_t i = 0; i < index; i++) {currentNode = currentNode->next;}currentNode->data = newValue; // 修改节点的值
}// 释放单向循环链表内存
void destroyCircularLinkedList(CircularLinkedList *list) {if (list->head == NULL) {return;}Node *currentNode = list->head;Node *nextNode;do {nextNode = currentNode->next;free(currentNode);currentNode = nextNode;} while (currentNode != list->head);list->head = NULL;list->size = 0;
}// 打印单向循环链表中的所有元素
void printCircularLinkedList(const CircularLinkedList *list) {if (list->head == NULL) {printf("链表为空\n");return;}Node *currentNode = list->head;do {printf("%d ", currentNode->data);currentNode = currentNode->next;} while (currentNode != list->head);printf("\n");
}// 主函数测试单向循环链表操作
int main() {CircularLinkedList myList; // 声明单向循环链表initCircularLinkedList(&myList); // 初始化单向循环链表printf("初始化单向循环链表成功!\n");insertAt(&myList, 0, 1); // 在索引0处插入元素1insertAt(&myList, 1, 2); // 在索引1处插入元素2insertAt(&myList, 2, 3); // 在索引2处插入元素3printf("向单向循环链表插入了3个元素\n");printCircularLinkedList(&myList); // 打印链表中的元素printf("单向循环链表长度为: %zu\n", myList.size); // 获取单向循环链表长度insertAt(&myList, 1, 4); // 在索引1处插入元素4printf("在索引1处插入元素4\n");printCircularLinkedList(&myList); // 打印链表中的元素printf("单向循环链表长度为: %zu\n", myList.size); // 再次获取单向循环链表长度printf("删除索引1处的元素,该元素值是: %d\n", deleteAt(&myList, 1)); // 删除索引1处的元素printCircularLinkedList(&myList); // 打印链表中的元素destroyCircularLinkedList(&myList); // 销毁单向循环链表printf("单向循环链表销毁成功!\n");return 0;
}

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

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

相关文章

Spring Boot集成jacoco实现单元测试覆盖统计

1.什么是jacoco&#xff1f; JaCoCo&#xff0c;即 Java Code Coverage&#xff0c;是一款开源的 Java 代码覆盖率统计工具。支持 Ant 、Maven、Gradle 等构建工具&#xff0c;支持 Jenkins、Sonar 等持续集成工具&#xff0c;支持 Java Agent 技术远程监控 Java 程序运行情况…

【鸿蒙学习笔记】Stage模型工程目录

官方文档&#xff1a;应用配置文件概述&#xff08;Stage模型&#xff09; 目录标题 FA模型和Stage模型工程级目录模块级目录app.json5module.json5程序执行流程程序基本结构开发调试与发布流程 FA模型和Stage模型 工程级目录 模块级目录 app.json5 官方文档&#xff1a;app.j…

STM32学习历程(day3)

通过GPIO点灯 首先先创建工程 这步比较繁琐 可以去参考江协科技[3-2]章节 想要驱动LED灯 要先使能时钟、然后再初始化、GPIO模式、引脚、以及输出速率 可以查看RCC的头文件 能看到三个使能函数 使能AHB、APB2、APB1 &#xff0c;GPIO用APB2这个函数、 通过看RCC库函数的源码…

给我的 IM 系统加上监控两件套:【Prometheus + Grafana】

监控是一个系统必不可少的组成部分&#xff0c;实时&#xff0c;准确的监控&#xff0c;将会大大有助于我们排查问题。而当今微服务系统的话有一个监控组合很火那就是 Prometheus Grafana&#xff0c;嘿你别说 这俩兄弟配合的相当完美&#xff0c;Prometheus负责数据采集&…

【MySQL系列】VARCHAR 类型详解及其使用策略

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

MySQL---事务管理

1.关于事务 理解和学习事务&#xff0c;不能只站在程序猿的角度来理解事务&#xff0c;而是要站在使用者&#xff08;用户&#xff09;的角度来理解事务。 比如支付宝转账&#xff0c;A转了B100块前&#xff0c;在程序猿的角度来看&#xff0c;是两条update操作&#xff0c;A …

浅谈反射机制

1. 何为反射&#xff1f; 反射&#xff08;Reflection&#xff09;机制指的是程序在运行的时候能够获取自身的信息。具体来说&#xff0c;反射允许程序在运行时获取关于自己代码的各种信息。如果知道一个类的名称或者它的一个实例对象&#xff0c; 就能把这个类的所有方法和变…

【贪心 堆 优先队列】502. IPO

本文涉及知识点 贪心 堆 优先队列 LeetCode502. IPO 假设 力扣&#xff08;LeetCode&#xff09;即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司&#xff0c;力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限&#xff0c;它只能在 IPO 之前完成最多 k…

ORB-SLAM3源码分析(案例分析)

一、ORB-SLAM3简介 ORB-SLAM3 (Oriented FAST and Rotated BRIEF SLAM 3) 是一种视觉SLAM&#xff08;Simultaneous Localization and Mapping&#xff0c;同时定位与地图构建&#xff09;系统&#xff0c;用于机器人和计算机视觉领域。它是ORB-SLAM系列的第三个版本&#xff…

非参数检测2——定义

定义&#xff1a;若研究二判定问题&#xff08;即判断有无信号&#xff09;的检测问题&#xff0c; 检测器的虚警概率可以由对输入数据统计特性提出微弱假设确定假设中不包含输入噪声的统计特性 则称该检测器为非参数检测器。 设计目标 在未知或时变环境下&#xff0c;有最…

【自动驾驶仿真在做什么——初学者总结(陆续补充)】

文章目录 基础概念自动驾驶级别再稍提一下ODD是什么&#xff1f; 自动驾驶仿真分类软件在环仿真硬件仿真 仿真究竟难在哪&#xff1f;关于lidar和radar区别一些名词解释 最近也是学习自动驾驶仿真相关知识&#xff0c;习惯去总结一下&#xff0c;方便自己回顾和总结&#xff0c…

【多媒体】富客户端应用程序GUI框架 JavaFX 2.0 简介

JavaFX 最初是由 Oracle 推出的一个用于开发富客户端应用程序的框架&#xff0c;它提供了丰富的用户界面控件、布局容器、3D图形绘制、媒体播放和动画等功能&#xff0c;旨在取代较旧的 Swing 框架。JavaFX 于 2007 年推出&#xff0c;2011 年 10 月发布了2.0 版本。JavaFX 2.0…

强强联合 | 人大金仓携手中国一汽引领国产数据库行业新浪潮

在国产化政策的推动下&#xff0c;人大金仓携手中国一汽联合开发更贴近汽车产业特定需求的数据库功能和组件。从2023年2月至今&#xff0c;人大金仓已累计部署690套数据库&#xff0c;适配应用系统170个&#xff0c;支撑中国一汽20多个核心系统和重要系统。目前&#xff0c;中国…

Okhttp hostnameVerifier详解

hostnameVerifier 方法简介核心原理参考资料 方法简介 本篇博文以Okhttp 4.6.0来解析hostnameVerfier的作用&#xff0c;顾名思义&#xff0c;该方法的主要作用就是鉴定hostnname的合法性。Okhttp在初始化的时候我们可以自己配置hostnameVerfier&#xff1a; new OkHttpClien…

计算机网络——数据链路层(以太网)

目录 局域网的数据链路层 局域网可按照网络拓扑分类 局域网与共享信道 以太网的两个主要标准 适配器与mac地址 适配器的组成与运作 MAC地址 MAC地址的详细介绍 局域网的mac地址格式 mac地址的发送顺序 单播、多播&#xff0c;广播mac地址 mac帧 如何取用…

YOLOX算法实现血细胞检测

原文:YOLOX算法实现血细胞检测 - 知乎 (zhihu.com) 目标检测一直是计算机视觉中比较热门的研究领域。本文将使用一个非常酷且有用的数据集来实现YOLOX算法,这些数据集具有潜在的真实应用场景。 问题陈述 数据来源于医疗相关数据集,目的是解决血细胞检测问题。任务是通过显微…

Linux基础指令及mysql(DQL)

[rootcentos ~]# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/export/server/jdk/bin:/root/binls在/usr/bin/路径下 [rootcentos ~]# which ls alias lsls --colorauto/usr/bin/lschmod ux,gx,o-r work.txt 可以对文件的权限进行修改。 sudo chown 修…

Python从入门到放弃——整数类型变量

变量 前言 上一篇文章中我们学习了Print函数&#xff0c;并且深入的理解了Print函数的各个参数。明确了应该如何利用各种参数来实现我们想输出的效果。那么现在让我们来学习一下变量这一个知识点。 什么是变量 变量&#xff0c;作为编程中的核心概念之一&#xff0c;其重要性…

STM32和DHT11使用显示温湿度度(代码理解)+单总线协议

基于STM32CT&#xff0c;利用DHT11采集温湿度数据&#xff0c;在OLED上显示。一定要阅读DHT11数据手册。 1、 DHT11温湿度传感器 引脚说明 1、VDD 供电3.3&#xff5e;5.5V DC 2、DATA 串行数据&#xff0c;单总线 3、NC 空脚 4、GND 接地&#xff0c;电源负极 硬件电路 微…

docker部署kafka(单节点) + Springboot集成kafka

环境&#xff1a; 操作系统&#xff1a;win10 Docker&#xff1a;Docker Desktop 4.21.1 (114176)、Docker Engine v24.0.2 SpringBoot&#xff1a;2.7.15 步骤1&#xff1a;创建网络&#xff1a; docker network create --subnet172.18.0.0/16 net-kafka 步骤2&#xff1a;安…