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

文章目录

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.mfbz.cn/a/776350.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt 基础组件速学 鼠标和键盘事件

学习目标&#xff1a; 鼠标事件和键盘事件应用 前置环境 运行环境:qt creator 4.12 学习内容和效果演示&#xff1a; 1.鼠标事件 根据鼠标的坐标位置&#xff0c;做出对应的事件。 2.键盘事件 根据键盘的输入做出对应操作 详细主要代码 1.鼠标事件 #include "main…

C++新特性

C新特性主要体现在语法改进和标准库扩充两个方面。以下是一些主要的C新特性&#xff1a; 语法改进 统一的初始化方法&#xff1a;C11扩大了用大括号括起的列表&#xff08;初始化列表&#xff09;的使用范围&#xff0c;使其可用于所有的内置类型和用户自定义的类型。这种定义…

vue.js微商城后台管理系统

一.需要运行的效果 20240701-231456 二.代码&#xff08;解析&#xff09; 首先&#xff0c;为项目添加依赖&#xff1a; yarn add element-plus --save yarn vue-router4 --save 新建一个项目包&#xff0c;然后命名为商品管理&#xff0c;在components中新建几个vue文件。 …

全新UI自助图文打印系统小程序源码 PHP后端 附教程

最新自助图文打印系统和证件照云打印小程序源码PHP后端&#xff0c;为用户用户自助打印的服务&#xff0c;包括但不限于文档、图片、表格等多种格式的文件。此外&#xff0c;它们还提供了诸如美颜、换装、文档打印等功能&#xff0c;以及后台管理系统&#xff0c;方便管理员对打…

TreeMap、HashMap 和 LinkedHashMap 的区别

TreeMap、HashMap 和 LinkedHashMap 的区别 1、HashMap2、LinkedHashMap3、TreeMap4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在 Java 中&#xff0c;TreeMap、HashMap 和 LinkedHashMap 是三种常用的集合类&#xff0c;它们在…

Ubuntu配置GitHub(第一次clone/push)

文章目录 1. 安装Git&检查连接2. 注册GitHub3. 生成&GitHub添加SSH3.1. 检查&删除已有id_rsa3.2. 生成SSH3.3. GitHub添加id_rsa.pub SSH3.4. 检查SSH 4. 继续开发可以参考参考 1. 安装Git&检查连接 安装 sudo apt-get install git检查SSH连接 ssh -T gitgi…

Qt 基础组件速学 事件过滤器

学习目标&#xff1a;理解事件过滤器 前置环境 运行环境:qt creator 4.12 学习内容和效果演示&#xff1a; Qt 提供了事件过滤器的机制,允许我们在事件到达目标对象之前对事件进行拦截和处理。这在以下情况下非常有用: 全局事件处理: 我们可以在应用程序级别安装一个事件过…

数据结构——(双)链表

文章目录 1. 定义 2. 双链表和单链表的区别 3. 代码示例 3.1 双链表节点和结构定义 3.2 初始化双链表 3.3 返回双链表的长度 3.4 在指定位置插入元素 3.5 在末尾插入元素 3.6 删除指定位置的元素并返回被删除的元素 3.7 删除末尾元素 3.8 获取指定位置的元素 3.9 修…

【IT领域新生必看】探索Java中的对象创建:深入理解`new`与`clone`的对比

文章目录 引言什么是new关键字&#xff1f;使用new关键字的基本语法示例&#xff1a; 什么是clone方法&#xff1f;使用clone方法的基本语法示例&#xff1a; new与clone的区别内存分配与初始化调用方式适用场景性能 new关键字的优缺点优点缺点 clone方法的优缺点优点缺点 深入…

机器学习---线性回归

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

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

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

评价ChatGPT与强人工智能的未来

在人工智能领域&#xff0c;ChatGPT的出现无疑是一个里程碑事件。它不仅展示了自然语言处理技术的巨大进步&#xff0c;也引发了人们对于强人工智能&#xff08;AGI&#xff09;的无限遐想。本文将从多个角度评价ChatGPT&#xff0c;并探讨强人工智能距离我们还有多远。 ChatGP…

【Leetcode笔记】406.根据身高重建队列

文章目录 1. 题目要求2.解题思路 注意3.ACM模式代码 1. 题目要求 2.解题思路 首先&#xff0c;按照每个人的身高属性&#xff08;即people[i][0]&#xff09;来排队&#xff0c;顺序是从大到小降序排列&#xff0c;如果遇到同身高的&#xff0c;按照另一个属性&#xff08;即p…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【密钥导入介绍及算法规格】

密钥导入介绍及算法规格 如果业务在HUKS外部生成密钥&#xff08;比如应用间协商生成、服务器端生成&#xff09;&#xff0c;业务可以将密钥导入到HUKS中由HUKS进行管理。密钥一旦导入到HUKS中&#xff0c;在密钥的生命周期内&#xff0c;其明文仅在安全环境中进行访问操作&a…

类继承-多继承虚继承

#include<iostream> using namespace std; class A1 { public:int a 10;}; class A2 { public:int b 20; }; class B :public A1, public A2 { public:int c 30; }; int main(){B b;cout << b.a << b.b << b.c << endl;return 0; } 如果基类…

十五、小型电脑没有数字键及insert,怎么解决IDEA快速插入getset构造这些方法

&#x1f33b;&#x1f33b;目录 一、小型电脑没有数字键及insert&#xff0c;怎么解决IDEA快速插入getset构造这些方法 一、小型电脑没有数字键及insert&#xff0c;怎么解决IDEA快速插入getset构造这些方法 解决&#xff1a; 1.winR打开搜索 2.osk回车 屏幕就出现了这样的一…

windows USB 设备驱动开发- 不同模型下的控制传输

在不同的模型下&#xff0c;USB控制传输会有不同的特点&#xff0c;但是任何控制传输的目标都始终是默认端点。 接收者是设备的实体&#xff0c;其信息&#xff08;描述符、状态等&#xff09;是主机感兴趣的。请求可进一步分为&#xff1a;配置请求、功能请求和状态请求。 发…

二刷力扣——单调栈

739. 每日温度 单调栈应该从栈底到栈顶 是递减的。 找下一个更大的 &#xff0c;用递减单调栈&#xff0c;就可以确定在栈里面的每个比当前元素i小的元素&#xff0c;下一个更大的就是这个i&#xff0c;然后弹出并记录&#xff1b;然后当前元素i入栈&#xff0c;仍然满足递减…

基于.NET开源游戏框架MonoGame实现的开源项目合集

前言 今天分享一些基于.NET开源游戏框架MonoGame实现的开源项目合集。 MonoGame项目介绍 MonoGame是一个简单而强大的.NET框架&#xff0c;使用C#编程语言可以创建桌面PC、视频游戏机和移动设备游戏。它已成功用于创建《怒之铁拳4》、《食肉者》、《超凡蜘蛛侠》、《星露谷物…

linux之管道重定向

管道与重定向 一、重定向 将原输出结果存储到其他位置的过程 标准输入、标准正确输出、标准错误输出 ​ 进程在运行的过程中根据需要会打开多个文件&#xff0c;每打开一个文件会有一个数字标识。这个标识叫文件描述符。 进程使用文件描述符来管理打开的文件&#xff08;FD--…