数据结构:循环单链表

server/2025/1/9 12:24:45/

循环单链表(Circular Singly Linked List)

循环单链表是单链表的一种变体,其特点是链表的尾节点指向头节点,形成一个闭环。这种结构允许在链表中进行无缝的遍历,并且可以从任何节点开始遍历整个链表。循环单链表通常用于需要循环访问元素的场景,如轮询调度、环形缓冲区等。

1. 节点结构

在循环单链表中,每个节点包含两个部分:

  • 数据:存储实际的数据元素。

  • 后继指针:指向当前节点的下一个节点。

class Node:def __init__(self, data):self.data = dataself.next = None
2. 循环单链表的特点
  • 循环结构:链表的尾节点指向头节点,形成一个闭环。

  • 单向遍历:由于每个节点只有一个指针,只能向前遍历链表。

  • 插入和删除操作高效:在插入或删除节点时,只需要修改相关节点的指针,不需要遍历整个链表,时间复杂度为O(1),假设你已经定位到操作的位置。

  • 内存消耗较低:每个节点只需要存储一个指针,内存消耗比双向链表低。

  • 实现复杂度较低:相对于双向链表,实现起来较为简单,但需要注意循环结构的特殊处理。

3. 循环单链表的操作
3.1 插入节点

在循环单链表中插入一个新节点,需要更新相关节点的指针:

  • 在头部插入

    1. 创建新节点。

    2. 如果链表为空,将新节点的next指向自己,并设置头节点为新节点。

    3. 如果链表不为空,设置新节点的next指向原头节点,并更新尾节点的next指向新节点。

    4. 更新头节点为新节点。

  • 在尾部插入

    1. 创建新节点。

    2. 如果链表为空,将新节点的next指向自己,并设置头节点为新节点。

    3. 如果链表不为空,设置新节点的next指向头节点,并更新原尾节点的next指向新节点。

    4. 更新尾节点为新节点。

3.2 删除节点

删除一个节点,需要更新其前驱节点的指针:

  • 删除头节点

    1. 如果链表为空,返回。

    2. 如果链表只有一个节点,释放该节点并设置头节点为空。

    3. 如果链表有多个节点,设置尾节点的next指向头节点的next

    4. 释放头节点,并更新头节点为原头节点的next

  • 删除尾节点

    1. 如果链表为空,返回。

    2. 如果链表只有一个节点,释放该节点并设置头节点为空。

    3. 如果链表有多个节点,找到尾节点的前驱节点,设置其next指向头节点。

    4. 释放尾节点,并更新尾节点为原尾节点的前驱节点。

3.3 查找节点

与单链表类似,可以从头节点开始遍历链表,逐个检查节点的数据是否匹配。由于链表是循环的,遍历会在回到头节点时停止。

4. 循环单链表的应用
  • 循环缓冲区:用于实现环形缓冲区,数据结构的两端相连,可以实现高效的循环读写。

  • 任务调度:在操作系统中,用于实现循环调度算法(如轮询调度)。

  • 多人游戏:在多人游戏中,玩家列表可以表示为一个循环单链表,允许玩家在列表中循环移动。

  • 循环队列:用于实现循环队列,避免数据搬移,提高效率。

5. 代码示例

以下是一个完整的循环单链表实现,包括插入、删除和遍历操作。

#include <stdio.h>
#include <stdlib.h>// 定义链表节点结构
typedef struct Node {int data;            // 数据域struct Node* next;   // 指向下一个节点的指针
} Node;// 定义循环单链表结构
typedef struct CircularLinkedList {Node* head;          // 指向链表的头节点
} CircularLinkedList;// 创建一个新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = NULL;return newNode;
}// 初始化循环单链表
void initCircularLinkedList(CircularLinkedList* cll) {cll->head = NULL;
}// 在循环单链表的末尾插入节点
void append(CircularLinkedList* cll, int data) {Node* newNode = createNode(data);if (cll->head == NULL) {// 如果链表为空,新节点成为头节点,并指向自身cll->head = newNode;newNode->next = cll->head;} else {// 遍历到链表的最后一个节点Node* current = cll->head;while (current->next != cll->head) {current = current->next;}// 将新节点插入到末尾,并指向头节点current->next = newNode;newNode->next = cll->head;}
}// 在循环单链表的开头插入节点
void prepend(CircularLinkedList* cll, int data) {Node* newNode = createNode(data);if (cll->head == NULL) {// 如果链表为空,新节点成为头节点,并指向自身cll->head = newNode;newNode->next = cll->head;} else {// 遍历到链表的最后一个节点Node* current = cll->head;while (current->next != cll->head) {current = current->next;}// 将新节点插入到开头,并指向原头节点newNode->next = cll->head;cll->head = newNode;current->next = cll->head;  // 更新最后一个节点的next指针}
}// 删除链表中第一个值为key的节点
void delete(CircularLinkedList* cll, int key) {if (cll->head == NULL) {return;  // 链表为空,直接返回}// 如果要删除的节点是头节点if (cll->head->data == key) {if (cll->head->next == cll->head) {// 链表只有一个节点free(cll->head);cll->head = NULL;} else {// 找到最后一个节点Node* last = cll->head;while (last->next != cll->head) {last = last->next;}// 将最后一个节点的next指向新的头节点Node* temp = cll->head;cll->head = cll->head->next;last->next = cll->head;free(temp);  // 释放原头节点}return;}// 如果要删除的节点不是头节点Node* current = cll->head;Node* prev = NULL;while (current->next != cll->head) {prev = current;current = current->next;if (current->data == key) {// 找到要删除的节点prev->next = current->next;free(current);return;}}
}// 打印循环单链表中的所有节点
void display(CircularLinkedList* cll) {if (cll->head == NULL) {printf("List is empty\n");return;}Node* current = cll->head;do {printf("%d -> ", current->data);current = current->next;} while (current != cll->head);printf(" (回到起点)\n");
}// 主函数
int main() {CircularLinkedList cll;initCircularLinkedList(&cll);// 在链表末尾插入元素append(&cll, 1);append(&cll, 2);append(&cll, 3);display(&cll);  // 输出: 1 -> 2 -> 3 ->  (回到起点)// 在链表开头插入元素prepend(&cll, 0);display(&cll);  // 输出: 0 -> 1 -> 2 -> 3 ->  (回到起点)// 删除元素delete(&cll, 2);display(&cll);  // 输出: 0 -> 1 -> 3 ->  (回到起点)return 0;
}

代码说明:

  1. Node 结构体:

     定义了链表中的节点,包含数据(data)和指向下一个节点的指针(next)。
  2. CircularLinkedList 结构体:

     定义了循环单链表,包含一个指向头节点的指针(head)。
  3. initCircularLinkedList 函数:

     初始化循环单链表,将 head 设为 NULL
  4. append 函数:

     在链表末尾插入一个新节点。如果链表为空,新节点成为头节点并指向自身;否则,遍历到链表的最后一个节点,将其 next 指向新节点,新节点的 next 指向头节点。
  5. prepend 函数:

     在链表开头插入一个新节点。如果链表为空,操作同 append;否则,找到最后一个节点,将其 next 指向新节点,新节点的 next 指向原头节点,然后更新头节点为新节点。
  6. delete 函数:

     删除链表中第一个值为 key 的节点。如果链表为空,直接返回;如果要删除的是头节点,需要特殊处理;否则,遍历链表找到要删除的节点,修改前一个节点的 next 指针。
  7. display 函数:

     打印链表中的所有节点数据。从头节点开始,遍历直到回到头节点,形成一个循环。

输出示例:

1 -> 2 -> 3 ->  (回到起点)
0 -> 1 -> 2 -> 3 ->  (回到起点)
0 -> 1 -> 3 ->  (回到起点)
6. 总结

循环单链表通过维护尾节点指向头节点的指针,形成一个闭环结构,提供了无缝遍历和高效插入删除操作的能力。虽然在内存消耗和实现复杂度上较低,但在需要频繁插入和删除操作,并需要无缝遍历的场景中,循环单链表具有很大的优势。


http://www.ppmy.cn/server/156118.html

相关文章

Spring Boot(快速上手)

Spring Boot 零、环境配置 1. 创建项目 2. 热部署 添加依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional> </dependency&…

【pyqt】(二)基础框架

基础框架 qt的主要窗口有三种类型&#xff1a;QMainWindow、QWeidget、QDialog。 QMainWindow提供了一个包含菜单栏、工具栏、状态栏和中心部件的完整框架。QMainWindow 适合用作应用程序的主要窗口&#xff0c;特别是当需要复杂的用户界面布局时。QWeidget是轻量级的&#x…

Django 管理界面实现自动提交和动态字段选项

在开发 Django 项目时,我们经常需要自定义管理界面以提高数据输入的效率和用户体验。本文将介绍如何在 Django 管理界面中实现自动提交功能和动态字段选项,以满足复杂的数据管理需求。 背景 假设我们正在开发一个 ECS(Elastic Compute Service)服务管理系统。在这个系统中…

TestEngine with ID ‘junit-jupiter‘ failed to discover tests 解决方法

SpringBoot2.3.12 在使用测试用例时报org.junit.platform.commons.JUnitException: TestEngine with ID junit-jupiter failed to discover tests 错误这个原因主要是idea 测试用例启动配置时错误造成。在idea启动调试的时候会提示 默认点击JAR mainfest得实时idea有时会自动设…

044_Standalone App in Matlab中发布独立应用

Matalb中应用部署 Matlab因为年头久远&#xff0c;所有的东西都积累了一大堆。就说是应用部署&#xff0c;Matlab 2023b至少有下面的几个技术线 Matlab Compiler技术线&#xff1a;产生独立App可执行程序或者网页应用Simulink Compiler技术线&#xff1a;产生独立App可执行程…

Edge安装问题,安装后出现:Could not find Edge installation

解决&#xff1a;需要再安装&#xff08;MicrosoftEdgeWebView2RuntimeInstallerX64&#xff09;。 网址&#xff1a;https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/?formMA13LH#download 如果已经安装了edge&#xff0c;那就再下载中间这个独立程序安装就…

大语言模型提示技巧(二)-给模型时间思考

在与大语言模型交互的时候&#xff0c;如果模型给出了错误的结论&#xff0c;不要着急否定大模型的能力&#xff0c;我们应当尝试重新构建查询&#xff0c;请求模型在提供它的最终答案之前进行一系列相关的推理。也就是说&#xff0c;如果给模型一个在短时间或用少量文字无法完…

SwanLab x LLaMA Factory:国产开源AI训练工具组合拳(含教程)

我们非常高兴地宣布SwanLab与LLaMA Factory建立合作伙伴关系&#xff0c;致力于为中国训练者提供优质、高效的大模型训练体验。 现在你使用新版本的LLaMA Factory启动训练前&#xff0c;可以在WebUI的「SwanLab configurations」&#xff08;中文&#xff1a;SwanLab参数设置&…