【C 数据结构】循环链表

news2025/2/27 18:00:16

文章目录

  • 【 1. 基本原理 】
  • 【 2. 循环链表的创建 】
    • 2.1 循环链表结点设计
    • 2.2 循环单链表初始化
  • 【 3. 循环链表的 插入 】
  • 【 4. 循环单链表的 删除操作 】
  • 【 5. 循环单链表的遍历 】
  • 【 6. 实例 - 循环链表的 增删查改 】
  • 【 7. 双向循环链表 】

【 1. 基本原理 】

  • 对于单链表以及双向链表,其就像一个小巷,无论怎么样最终都能从一端走到另一端,然而循环链表则像一个有传送门的小巷,因为循环链表当你以为你走到结尾的时候,其实你又回到了开头。
  • 循环链表和非循环链表其实创建的过程以及思路几乎完全一样,唯一不同的是,非循环链表的尾结点指向空(NULL),而 循环链表的尾指针指向的是链表的开头。通过将单链表的尾结点指向头结点的链表称之为 循环单链表(Circular linkedlist)。如下图所示:
    在这里插入图片描述

【 2. 循环链表的创建 】

2.1 循环链表结点设计

  • 以单循环链表为例,对于循环单链表的结点,可以完全参照于单链表的结点设计,如图:
    • data表示数据,其可以是简单的类型(如int,double等等),也可以是复杂的结构体(struct类型)
    • next表示指针,它永远指向自身的下一个结点, 对于 只有一个结点的存在,这个next指针则永远指向自身,对于一个 循环链表的尾部结点,next永远指向开头。
      在这里插入图片描述
  • 其代码可以表示为:
typedef struct list
{
    int data;
    struct list *next;
}list;
//data为存储的数据,next指针为指向下一个结点

2.2 循环单链表初始化

  • 如同单链表的创建,我们需要先创建一个头结点并且给其开辟内存空间,但与单链表不同的是,我们需要在开辟内存空间成功之后将头结点的next指向head自身。我们可以创建一个init函数来完成这件事情,为了以后的重复创建和插入,我们可以考虑 在init重创建的结点next指向空,而在主函数调用创建之后手动 将head头结点的next指针指向自身。这样的操作方式可以方便过后的创建单链表,直接利用多次调用的插入函数即可完成整体创建。
  • C语言实现可以表示为:
//初始结点
list *initlist()
{
    list *head=(list*)malloc(sizeof(list));
    if(head==NULL)
    {
        printf("创建失败,退出程序");
        exit(0);
    }
    else
    {
        head->next=NULL;
        return head;
    }
}
  • 在主函数重调用:
//初始化头结点
list *head=initlist();
head->next=head;

【 3. 循环链表的 插入 】

  • 对于插入数据的操作,基本与单链表的插入操作相同,我们可以创建一个独立的结点,通过将需要插入的结点的上一个结点的next指针指向该节点,再由需要插入的结点的next指针指向下一个结点的方式完成插入操作。
    在这里插入图片描述
  • C 代码可以表示为:
//插入元素
//三个参数分别是链表,位置,参数
list *insert_list(list *head,int pos,int data)
{
    list *node=initlist();  //新建结点
    list *p=head;       //p表示新的链表
    list *t;
    t=p;
    node->data=data;
    if(head!=NULL)
    {
        for(int i=1;i<pos;i++)
        {
            t=t->next;  //走到需要插入的位置处
        }
        node->next=t->next;
        t->next=node;
        return p;
    }
    return p;
}

【 4. 循环单链表的 删除操作 】

  • 如图所示,循环单链表的删除操作可以参考单链表的删除操作,其都是 找到需要删除的结点,将其前一个结点的next指针直接指向删除结点的下一个结点 即可,但需要注意的是尾节点和头结点的特判,尤其是尾结点,因为删除尾节点后,尾节点前一个结点就成了新的尾节点,这个新的尾节点需要指向的是头结点而不是空,其重点可以记录为【删除节点的前一节点.next=删除结点.next】这样的操作可以省去头尾结点的特判:
    在这里插入图片描述
  • C 代码:
//删除元素
int delete_list(list *head) 
{
    if(head == NULL) 
    {
        printf("链表为空!\n");
        return 0;
    }
    //建立临时结点存储头结点信息(目的为了找到退出点)
    //如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出
    //循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的
    list *temp = head; //作为遍历的前一个节点         
    list *ptr = head->next;//作为遍历的当前节点

    int del;
    printf("请输入你要删除的元素:");
    scanf("%d",&del);

    while(ptr != head) 
    {
        if(ptr->data == del) 
        {
            if(ptr->next == head) //如果是最后一个尾节点
            {
                temp->next = head;
                free(ptr);
                return 1;
            }
            temp->next = ptr->next;    //核心删除操作代码
            free(ptr);
            //printf("元素删除成功!\n");
            return 1;
        }
        temp = temp->next;
        ptr = ptr->next;
    }
    printf("没有找到要删除的元素\n");
    return 0;
}

【 5. 循环单链表的遍历 】

  • 与普通的单链表和双向链表的遍历不同,循环链表需要进行结点的判断,找到尾节点的位置,由于尾节点的next指针是指向头结点的,所以不能使用链表本身是否为空(NULL)的方法进行简单的循环判断,我们需要通过判断结点的next指针是否等于头结点的方式进行是否完成循环的判断
  • 此外还有一种计数的方法,即建立一个计数器count=0,每一次next指针指向下一个结点时计数器加一,当count数字与链表的节点数相同的时候即完成循环,这样做有一个 问题就是获取到链表的节点数同时也需要完成一次遍历才可以达成目标
  • C代码:
//遍历元素
int display(list *head) 
{
    if(head != NULL) 
    {
        list *p  = head;
        //遍历头节点到,最后一个数据
        while(p->next != head ) 
        {
            printf("%d   ",p->next->data);
            p = p->next;
        }
        printf("\n");   //习惯性换行
        //把最后一个节点赋新的节点过去
        return 1;
    }
    else
    {
        printf("头结点为空!\n");
        return 0;
    }
}

【 6. 实例 - 循环链表的 增删查改 】

 #include<stdio.h>
#include<stdlib.h>
typedef struct list{
    int data;
    struct list *next;
}list;
//data为存储的数据,next指针为指向下一个结点
 
//初始结点
list *initlist(){
    list *head=(list*)malloc(sizeof(list));
    if(head==NULL){
        printf("创建失败,退出程序");
        exit(0);
    }else{
        head->next=NULL;
        return head;
    }
}
 
//创建--插入数据
int create_list(list *head){
    int data;   //插入的数据类型
    printf("请输入要插入的元素:");
    scanf("%d",&data);
 
    list *node=initlist();
    node->data=data;
    //初始化一个新的结点,准备进行链接
 
    if(head!=NULL){
        list *p=head;
        //找到最后一个数据
        while(p->next!=head){
            p=p->next;
        }
        p->next=node;
        node->next=head;
        return 1;
    }else{
        printf("头结点已无元素\n");
        return 0;
    }
 
}
 
//插入元素
list *insert_list(list *head,int pos,int data){
    //三个参数分别是链表,位置,参数
    list *node=initlist();  //新建结点
    list *p=head;       //p表示新的链表
    list *t;
    t=p;
    node->data=data;
    if(head!=NULL){
        for(int i=1;i<=pos;i++){
            t=t->next;
        }
        node->next=t->next;
        t->next=node;
        return p;
    }
    return p;
}
 
//删除元素
int delete_list(list *head) {
    if(head == NULL) {
        printf("链表为空!\n");
        return 0;
    }
    //建立零时结点存储头结点信息(目的为了找到退出点)
    //如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出
    //循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的
    list *temp = head;          
    list *ptr = head->next;
 
    int del;
    printf("请输入你要删除的元素:");
    scanf("%d",&del);
 
    while(ptr != head) {
        if(ptr->data == del) {
            if(ptr->next == head) { //循环结束的条件换成ptr->next == head
                temp->next = head;
                free(ptr);
                return 1;
            }
            temp->next = ptr->next;
            free(ptr);
            //printf("元素删除成功!\n");
            return 1;
        }
        temp = temp->next;
        ptr = ptr->next;
    }
    printf("没有找到要删除的元素\n");
    return 0;
}
 
 
//遍历元素
int display(list *head) {
    if(head != NULL) {
        list *p  = head;
        //遍历头节点到,最后一个数据
        while(p->next != head ) {
            printf("%d   ",p->next->data);
            p = p->next;
        }
        printf("\n");   //习惯性换行 ( o=^•ェ•)o ┏━┓
        //把最后一个节点赋新的节点过去
        return 1;
    } else {
        printf("头结点为空!\n");
        return 0;
    }
}
 
int main(){
    //初始化头结点//
    list *head=initlist();
    head->next=head;
    通过插入元素完善链表/
    for(int i=0;i<5;i++){   //只是演示使用,不具体提供输入
        create_list(head);
    }
    display(head);
    插入元素
    head=insert_list(head,1,10);
    display(head);
    删除元素
    delete_list(head);
    display(head);
    return 0;
}

【 7. 双向循环链表 】

  • 循环链表还有一个进阶的概念练习,同双向链表与单链表的关系一样,循环单链表也有一个孪生兄弟——双向循环链表,其设计思路与单链表和双向链表的设计思路一样,就是 在原有的双向链表的基础上,将尾部结点和头部结点进行互相连接

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

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

相关文章

C语言数组:数据的集合艺术

在C语言的世界里&#xff0c;数组就像是一个魔术盒&#xff0c;里面装满了相同类型的宝藏。今天&#xff0c;就让我们一起揭开这个魔术盒的神秘面纱&#xff0c;探索数组的魅力所在。 一、数组的定义与初始化 数组&#xff0c;简单来说&#xff0c;就是一系列相同类型数据的…

深入浅出Redis(十二):Redis的排序命令Sort

引言 Redis是一款快速、优秀的键值对数据库&#xff0c;提供丰富的数据结构能在各种场景下实现功能&#xff0c;同时也提供丰富的命令来完成各种各样的功能&#xff0c;本篇文章将深入浅出的解析Sort命令的原理以及使用 原理 Sort 命令用来对list、set、zset对象进行排序&am…

Python爬虫高手必备的8大技巧!

想要快速学习爬虫&#xff0c;最值得学习的语言一定是Python&#xff0c;Python应用场景比较多&#xff0c;比如&#xff1a;**Web快速开发、爬虫、自动化运维等等&#xff0c;**可以做简单网站、自动发帖脚本、收发邮件脚本、简单验证码识别脚本。 爬虫在开发过程中也有很多复…

关于MCU产品开发参数存储的几种方案

关于MCU产品开发参数存储的几种方案 Chapter1 关于MCU产品开发参数存储的几种方案Chapter2 单片机参数处理[保存与读取]Chapter3 嵌入式设备参数存储技巧Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C) Chapter1 关于MCU产品开发参数存储的几种方案 原文链接 在工作中…

《系统架构设计师教程(第2版)》第9章-软件可靠性基础知识-02-软件可靠性建模

文章目录 1. 概述1.1 软件可靠性模型1.2 影响软件可靠性的因素 2. 软件可靠性的建模方法2.1 模型组成2.2 模型假设2.3 参数估计1&#xff09;确定参数的方法2&#xff09;故障预测 可靠性模型特性 3. 软件的可靠性模型分类3.1 种子法模型3.2 失效率类模型3.3.曲线拟合类模型3.4…

吴恩达2022机器学习专项课程(一) 第二周课程实验:特征缩放和学习率(多元)(Lab_03)

备注&#xff1a;笔者只对个人认为的重点代码做笔记&#xff0c;其它详细内容请参考吴恩达老师实验里的笔记。 1.多元特征的训练集 调用load_house_data()函数&#xff0c;将训练集数据保存到数组中。 X&#xff0c;y分别存储所有训练样本的前四列&#xff0c;所有训练样本的…

Harmony鸿蒙南向驱动开发-PWM

PWM&#xff08;Pulse Width Modulation&#xff09;即脉冲宽度调制&#xff0c;是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术&#xff0c;广泛应用在从测量、通信到功率控制与变换的许多领域中。通常情况下&#xff0c;在使用马达控制、背光亮度调节时会用到PWM模…

Java数据结构二叉树

概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1. 或者为空 2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。 从上图可以看出&#xff1a; 1. 二叉树不存在度大于2的结点 2. 二叉树的子树有左右之分&#xff0c;次序不能颠倒&#x…

音乐界Sora隆重发布!效果炸裂,超越Suno!根据指令生成定制音乐,原创续歌样样行!前谷歌Deepmind人员创建

火爆&#xff01;预热了一周的 Udio 终于发布了&#xff0c;可谓是吊足了网友们的胃口&#xff0c;从展示的效果来看&#xff0c;确实没让网友们白等&#xff01; 分享几个网站 GPT-3.5研究测试&#xff1a; https://hujiaoai.cn GPT-4研究测试&#xff1a; https://higpt4.…

VirusTaxo:病毒物种注释

https://github.com/omics-lab/VirusTaxo 安装 git clone https://github.com/omics-lab/VirusTaxo mamba create -n VirusTaxo python3.10 mamba activate VirusTaxo cd VirusTaxo python3 -m venv environment source ./environment/bin/activate pip install -r require…

【电控笔记4】拉普拉斯-传递函数-pid

数据标幺化 拉普拉斯变换 欧拉公式 常见s变换 s变换性质 pid分析 p控制&#xff0c;存在稳态误差 可以求出p的取值范围p>-1&#xff0c;否则发散 pi消除稳态误差 把kp换成Gs 只用pi控制&#xff0c;不加微分的原因&#xff1a; 微分之后&#xff0c;噪声增大高频噪声频率…

计算机组成原理(存储器)

1、“821.2016T1(1)”&#xff0c;表示821真题&#xff0c;2016年的题&#xff0c;T1是 选择题/填空题/大题 的第一题&#xff0c;其他类似标记也是相通 2、个人小白总结自用&#xff0c;不一定适用于其他人&#xff0c;请自行甄别 3、有任何疑问&#xff0c;欢迎私信探讨&…

Python测试框架之pytest详解

前言 Python测试框架之前一直用的是unittestHTMLTestRunner&#xff0c;听到有人说pytest很好用&#xff0c;所以这段时间就看了看pytest文档&#xff0c;在这里做个记录。 官方文档介绍&#xff1a; Pytest is a framework that makes building simple and scalable tests e…

常见开关电源的特殊波形有哪些?测试方法是什么?

开关电源特殊波形 1. 毛刺输入测试波形 毛刺是比较常见的波形&#xff0c;一般是由于元器件损坏或老化、电源噪声、信号干扰等因素造成的。这种波形的特点是电网尖锋有过冲并会跌落到0v&#xff0c;过冲和跌落脉宽很窄&#xff0c;一般不会大于100ms&#xff0c;过冲幅度一般不…

FFmpeg: 简易ijkplayer播放器实现--04消息队列设计

文章目录 播放器状态转换图播放器状态对应的消息&#xff1a; 消息对象消息队列消息队列api插入消息获取消息初始化消息插入消息加锁初始化消息设置消息参数消息队列初始化清空消息销毁消息启动消息队列终止消息队列删除消息 消息队列&#xff0c;用于发送&#xff0c;设置播放…

Spring 如何组织项目-ApiHug准备-工具篇-007

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace ApiHug …

贪心算法|452.用最少数量的箭引爆气球

力扣题目链接 class Solution { private:static bool cmp(const vector<int>& a, const vector<int>& b) {return a[0] < b[0];} public:int findMinArrowShots(vector<vector<int>>& points) {if (points.size() 0) return 0;sort(p…

【JavaEE】_Spring MVC项目获取Session

目录 1. 使用servlet原生方法获取Session 1.1 错误获取方法 1.2 正确获取方法 2. 使用Spring注解获取Session 3. 使用Spring内置对象获取Session 1. 使用servlet原生方法获取Session .java文件内容如下&#xff1a; setSession方法用于设置Session对象的内容&#xff1b;…

文件输入/输出流(I/O)

文章目录 前言一、文件输入\输出流是什么&#xff1f;二、使用方法 1.FileInputStream与FileOutputStream类2.FileReader与FileWriter类总结 前言 对于文章I/O(输入/输出流的概述)&#xff0c;有了下文。这篇文章将具体详细展述如何向磁盘文件中输入数据&#xff0c;或者读取磁…

面试八股——数据库——事务

概述 事务的特性&#xff08;ACID&#xff09; 原子性&#xff1a;事务是不可分割的操作单元&#xff0c;要么全部成功&#xff0c;要么全部失败。 一致性&#xff1a;事务结束时&#xff0c;所有数据都必须保证一致状态。 隔离性&#xff1a;事务在独立环境运行&#xff0c;…