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

news2025/4/19 7:44:09

文章目录

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处插入元素1
    insertAt(&myList, 1, 2); // 在索引1处插入元素2
    insertAt(&myList, 2, 3); // 在索引2处插入元素3
    printf("向单向循环链表插入了3个元素\n");
    printCircularLinkedList(&myList); // 打印链表中的元素

    printf("单向循环链表长度为: %zu\n", myList.size); // 获取单向循环链表长度

    insertAt(&myList, 1, 4); // 在索引1处插入元素4
    printf("在索引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.coloradmin.cn/o/1899402.html

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

相关文章

UCOS-III 系统移植

1. 移植前准备 1.1 源码下载 UCOS-III Kernel Source&#xff1a; https://github.com/weston-embedded/uC-OS3.git Micriμm CPU Source &#xff1a; https://github.com/weston-embedded/uC-CPU.git Micriμm Lib Source&#xff1a; https://github.com/weston-embedded…

Intellj idea无法启动

个人电脑上安装的是2024.01版本的intellj idea作为开发工具&#xff0c;引入了javaagent作为工具包 但是在一次invaliad cache操作后&#xff0c;intellj idea就无法启动了&#xff0c;双击无响应。 重装了idea后也无效&#xff08;这个是有原因的&#xff0c;下面会讲&#…

java项目总结4

1.正则表达式 用于验证字符串是否满足自己所需要的规则。方法&#xff1a;matches 注意&#xff1a;\在Java中有特殊涵义&#xff0c;是将其它的意思本来化&#xff0c;假设"是用来引用字符串的&#xff0c;但是你如果想要输出它&#xff0c;那是不是就变成了System.out…

机器学习---线性回归

1、线性回归 例如&#xff1a;对于一个房子的价格&#xff0c;其影响因素有很多&#xff0c;例如房子的面积、房子的卧室数量、房子的卫生间数量等等都会影响房子的价格。这些影响因子不妨用 x i x_{i} xi​表示&#xff0c;那么房价 y y y可以用如下公式表示&#xff1a; y …

基于Qt实现的PDF阅读、编辑工具

记录一下实现pdf工具功能 语言&#xff1a;c、qt IDE&#xff1a;vs2017 环境&#xff1a;win10 一、功能演示&#xff1a; 二、功能介绍&#xff1a; 1.基于saribbon主体界面框架&#xff0c;该框架主要是为了实现类似word导航项 2.加载PDF放大缩小以及预览功能 3.pdf页面跳转…

01--SpringAI接入大模型,chatgpt,Java接入人工智能大模型

01–SpringAI接入大模型,chatgpt,Java接入人工智能大模型 文章目录 01--SpringAI接入大模型,chatgpt,Java接入人工智能大模型一、准备工作&#xff1f;①&#xff1a;环境准备 二、创建一个springAI项目①&#xff1a;创建一个根项目②&#xff1a;创建一个SpringAI模块01.解决…

Dos(命令符窗口)命令

目录 1. 常用Windows组合键 2. 常用DOS命令 3. 复制操作 4. 当前路径 5. 查看电脑ip地址 6. 切换盘符: 7. 目录 8. 自动补齐 8. 进入某路径&#xff1a;cd 路径 9. 直接进入某个位置 10. 新建文件 11. 查看文件内容 12. 关机 13. 强行终止命令的执行&#xff1a;C…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

查看java版本和安装位置-cnblog

查看java位置 进入设置&#xff0c;高级系统设置 打开环境变量 找到path双击 查看java版本 java -version

中电金信:加快企业 AI 平台升级,构建金融智能业务新引擎

在当今数字化时代的浪潮下&#xff0c;人工智能&#xff08;AI&#xff09;技术的蓬勃发展正为各行业带来前所未有的变革与创新契机。尤其是在金融领域&#xff0c;AI 模型的广泛应用已然成为提升竞争力、优化业务流程以及实现智能化转型的关键驱动力。然而&#xff0c;企业在积…

刷代码随想录有感(126):动态规划——不相交的线

题干&#xff1a; 代码&#xff1a; class Solution { public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>>dp(nums1.size() 1, vector<int>(nums2.size() 1, 0));for(int i 1; i < num…

Github与本地仓库建立链接、Git命令(或使用Github桌面应用)

一、Git命令&#xff08;不嫌麻烦可以使用Github桌面应用&#xff09; git clone [] cd [] git branch -vv #查看本地对应远程的分支对应关系 git branch -a #查看本地和远程所有分支 git checkout -b [hongyuan] #以当前的本地分支作为基础新建一个【】分支,命名为h…

矢量绘图设计Sketch中文 Sketch直装安装包

Sketch是一款专为UI设计师和UX专家打造的矢量图形设计软件&#xff0c;以其简洁的界面、强大的功能和高效的协作能力而闻名。Sketch支持快速创建高质量的UI界面、图标、图形和插画&#xff0c;其矢量绘图工具让设计细节更加精准。同时&#xff0c;Sketch内置丰富的插件和组件库…

AI文字图片人脸生成原创视频文生图生肖生小程序开发

AI文字图片人脸生成原创视频文生图生肖生小程序开发 无限开 0.12生成 图生视频 AI技术在生成文字、图片、人脸以及视频方面已经取得了显著的进步。以下是一些可能包含在AI文字图片人脸生成原创视频小程序中的功能列表&#xff1a; 文字转视频&#xff1a; 输入文字或文章&…

使用flask的web网页部署介绍

使用flask的web网页部署介绍 文章目录 前言一、网页介绍二、数据库设计介绍总结 前言 flaskbootstrapjquerymysql搭建三叶青在线识别网站&#xff0c;使用nginxgunicorn将网站部署在腾讯云上&#xff0c;配置SSL证书。网站地址&#xff1a;https://www.whtuu.cn 三叶青图像识…

Open3D 在点云中构建八叉树

目录 一、概述 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2构建后点云 一、概述 八叉树&#xff08;Octree&#xff09;是一种树状数据结构&#xff0c;用于递归地将3D空间分割成较小的立方体。八叉树特别适用于3D计算机图形学、点云处理和空间…

uniapp实现可拖动悬浮按钮(最新版2024-7月)

此章主要介绍如何使用uniapp跨平台开发&#xff0c;实现悬浮按钮&#xff0c;移动端经常会有所这样的需求&#xff0c;那么功能如下&#xff1a; 1.圆圈悬浮球&#xff0c;上下左右靠边显示 2.可以界面任何拖动&#xff0c;不会超出界面 3.单击悬浮球的点击事件 效果&#xf…

Flash存储器解析:从原理到应用,全面了解其与缓存的区别

Flash存储器解析&#xff1a;从原理到应用&#xff0c;全面了解其与缓存的区别 Flash存储器是一种非易失性存储器技术&#xff0c;广泛应用于各种电子设备中&#xff0c;如USB闪存盘、固态硬盘&#xff08;SSD&#xff09;、智能手机、数码相机和嵌入式系统。它能够在断电情况下…

1-3 NLP为什么这么难做

1-3 NLP为什么这么难做 主目录点这里 字词结构的复杂性 中文以汉字为基础单位&#xff0c;一个词通常由一个或多个汉字组成&#xff0c;而不像英语词汇单元由字母构成。这使得中文分词&#xff08;切分句子为词语&#xff09;成为一个具有挑战性的任务。语言歧义性 中文中常…

day04-matplotlib入门

matplotlib Matplotlib 提供了一个套面向绘图对象编程的 API接口 是一款用于数据可视化的 Python 软件包&#xff0c;支持跨平台运行 它能够根据 NumPyndarray 数组来绘制 2D(3D) 图像&#xff0c;它使用简单、代码清晰易懂&#xff0c;深受广大技术爱好 者喜爱。 实列&…