检测链表中是否存在环

news2024/11/17 11:28:02

题目、解析和代码

题目:给定一个单链表,判断其中是否有环的存在
解析:这里使用两个遍历速度不一样的结点进行判断,一个慢结点从首结点开始遍历,这个结点每次只遍历一个结点;一个快结点从第二个结点进行遍历,一次遍历两个结点。
若是链表中存在环,那么必然在某个结点处相遇;若是没有环,那么尾结点后继指针指向空。
LinkedListChainTest.c代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct operatorNodeStruce {
    int data;
    struct operatorNodeStruce* next;
}operatorNode;

int main(int argc, char *argv[]) {
    if(argc == 1){
        printf("请输入链表中结点个数和环的接连位置\n");
        return 0;
    }else if(argc == 2){
        printf("请输入环的接连位置,负数表示没有环\n");
        return 0;
    }else if(argc > 3){
        printf("参数不能大于3个\n");
        return 0;
    }
    int nodeNumber = atoi(argv[1]);
    int chainNodeIndex = atoi(argv[2]);
    if(chainNodeIndex>nodeNumber){
        printf("环的接连位置应该小于链表中结点个数\n");
        return 0;
    }
    operatorNode *head = NULL;
    operatorNode *storeNode = NULL;
    int i=1;
    // 创建链表,这个链表有nodeNumber个结点
    for(;i<=nodeNumber;i++){
        operatorNode *newNode = (operatorNode *)malloc(sizeof(operatorNode));
        newNode->next = NULL;
        newNode->data = i;
        if(head == NULL){
            head = newNode;
            storeNode = head;
        }else{
            storeNode->next = newNode;
        }
        if(storeNode -> next != NULL){
            storeNode = storeNode -> next;
        }
    }

    //把最后一个结点的后继指针next指向第chainNodeIndex个结点,这样的话才能形成环
    operatorNode *chainNodeBefore = NULL;
    operatorNode *node= NULL;
    if(chainNodeIndex > 0){
        for(int i=0;i<chainNodeIndex;i++){
            if(node == NULL){
                node = head;
            }
            chainNodeBefore = node;
            node = node->next;
        }
    }
    storeNode->next = chainNodeBefore;

    // 使用快慢结点法来判断是否有环,slowNode从第一个结点开始往后遍历,每次只遍历一个结点;fastNode从第二个结点开始往后遍历,每次遍历二个结点;
    // 要是有环,快结点(fastNode)一定能够在某个结点处跟慢结点(slowNode)相遇
    operatorNode *fastNode = NULL;
    if(head->next != NULL){
        fastNode = head->next;
    }
    operatorNode *slowNode = head;
    while(true){
        if(fastNode== NULL){
            printf("无环\n");
            return 0;
        }else if(fastNode->next == NULL){
            printf("无环\n");
            return 0;
        }else if(fastNode->next->next == NULL){
            printf("无环\n");
            return 0;
        }else{
            if(slowNode == fastNode){
                printf("有环\n");
                return 0;
            }
            slowNode = slowNode->next;
            fastNode = fastNode->next->next;
        }
    }
    return 0;
}

gcc LinkedListChainTest.c -o LinkedListChainTest进行编译。
在这里插入图片描述

./LinkedListChainTest 6 4表示这个链表有6个结点,最后一个结点后继指针指向第4个结点,就像下图所示。
在这里插入图片描述

./LinkedListChainTest 7 2表示这个链表有7个结点,最后一个结点后继指针指向第2个结点,就像下图所示。
在这里插入图片描述

./LinkedListChainTest 8 3表示这个链表有8个结点,最后一个结点后继指针指向第3个结点,就像下图所示。
在这里插入图片描述

遍历解析

./LinkedListChainTest 6 4为例进行解析。
在这里插入图片描述
如上图,蓝色圆心虚线箭头为慢结点(slowNode)所在位置的标识。

在这里插入图片描述
如上图,绿色实心箭头为快结点(fastNode)所在位置的标识。

刚开始的实例图如下:
在这里插入图片描述

1次遍历之后,慢结点在第2个结点处,快结点在第4个结点处:
在这里插入图片描述

2次遍历之后,慢结点在第3个结点处,快结点在第6个结点处:
在这里插入图片描述

3次遍历之后,慢结点在第4个结点处,快结点在第5个结点处:
在这里插入图片描述

4次遍历之后,慢结点在第5个结点处,快结点在第5个结点处,可以判定有环:
在这里插入图片描述

复杂度分析

结点个数环的接连位置快结点在环中所落位置遍历次数
612、4、65
623、5、2、4、64
634、63
645、4、65
6565
6665
711、3、5、7、2、4、66
722、4、65
733、5、7、4、64
744、63
755、7、65
7665
7766

若链表中有2n(n>=1,是正整数,2n为偶数)个结点:

那么若环的连接位置x不大于n,那么遍历次数为2n-x
若是环的连接位置x大于n,则最多遍历次数小于等于2n-1,还没有总结出来公式。

若链表中有2n+1(n>=1,是正整数,2n+1为偶数)个结点:

那么环的连接位置x若不大于n+1,那么遍历次数为2n+1-x
若是环的连接位置x大于n,则最多遍历次数小于等于2n+1,还没有总结出来公式。

若是没有环的话,最多遍历次数是n/2
综上所述,最好时间复杂度和最坏时间复杂度都是O(n),平均时间复杂度也是O(n)
而空间复杂度是O(1),因为除了存储变量的必要空间,没有申请额外的空间。
因为需要更好地分析时间复杂度,所以,我把代码修改成下方所示:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct operatorNodeStruce {
    int data;
    struct operatorNodeStruce* next;
}operatorNode;

int main(int argc, char *argv[]) {
    if(argc == 1){
        printf("请输入链表中结点个数和环的接连位置\n");
        return 0;
    }else if(argc == 2){
        printf("请输入环的接连位置,负数表示没有环\n");
        return 0;
    }else if(argc > 3){
        printf("参数不能大于3个\n");
        return 0;
    }
    int nodeNumber = atoi(argv[1]);
    int chainNodeIndex = atoi(argv[2]);
    if(chainNodeIndex>nodeNumber){
        printf("环的接连位置应该小于链表中结点个数\n");
        return 0;
    }
    operatorNode *head = NULL;
    operatorNode *storeNode = NULL;
    int i=1;
    // 创建链表,这个链表有nodeNumber个结点
    for(;i<=nodeNumber;i++){
        operatorNode *newNode = (operatorNode *)malloc(sizeof(operatorNode));
        newNode->next = NULL;
        newNode->data = i;
        if(head == NULL){
            head = newNode;
            storeNode = head;
        }else{
            storeNode->next = newNode;
        }
        if(storeNode -> next != NULL){
            storeNode = storeNode -> next;
        }
    }


    //把最后一个结点的后继指针next指向第chainNodeIndex个结点,这样的话才能形成环
    operatorNode *chainNodeBefore = NULL;
    operatorNode *node= NULL;
    if(chainNodeIndex > 0){
        for(int i=0;i<chainNodeIndex;i++){
            if(node == NULL){
                node = head;
            }
            chainNodeBefore = node;
            node = node->next;
        }
    }
    storeNode->next = chainNodeBefore;



    // 使用快慢结点法来判断是否有环,slowNode从第一个结点开始往后遍历,每次只遍历一个结点;fastNode从第二个结点开始往后遍历,每次遍历二个结点;
    // 要是有环,快结点(fastNode)一定能够在某个结点处跟慢结点(slowNode)相遇
    operatorNode *fastNode = NULL;
    if(head->next != NULL){
        fastNode = head->next;
    }
    operatorNode *slowNode = head;
    int chainNodetimes = 0;
    while(true){
        if(chainNodetimes != 0){
            printf("fastNode data:%d\tslowNode data: %d\n",fastNode->data,slowNode->data);
        }
        if(fastNode== NULL){
            printf("无环\n");
            return 0;
        }else if(fastNode->next == NULL){
            printf("无环\n");
            return 0;
        }else if(fastNode->next->next == NULL){
            printf("无环\n");
            return 0;
        }else{
            if(slowNode == fastNode){
                printf("循环次数:%d\n",chainNodetimes);
                printf("有环\n");
                return 0;
            }
            slowNode = slowNode->next;
            fastNode = fastNode->next->next;
            chainNodetimes++;
        }
    }
    return 0;
}

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

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

相关文章

设计模式之八:迭代器与组合模式

有许多方法可以把对象堆起来成为一个集合&#xff08;Collection&#xff09;&#xff0c;比如放入数组、堆栈或散列表中。若用户直接从这些数据结构中取出对象&#xff0c;则需要知道具体是存在什么数据结构中&#xff08;如栈就用peek&#xff0c;数组[]&#xff09;。迭代器…

WinPlan经营大脑:精准预测,科学决策,助力企业赢得未来

近年,随着国内掀起数字化浪潮,“企业数字化转型”成为大势所趋下的必选项。但数据显示,大约79%的中小企业还处于数字化转型初期,在“企业经营管理”上存在着巨大的挑战和风险。 WinPlan经营大脑针对市场现存的企业经营管理难题,提供一站式解决方案,助力企业经营管理转型…

设计模式--代理模式

笔记来源&#xff1a;尚硅谷Java设计模式&#xff08;图解框架源码剖析&#xff09; 代理模式 1、代理模式的基本介绍 1&#xff09;代理模式&#xff1a;为一个对象提供一个替身&#xff0c;以控制对这个对象的访问。即通过代理对象访问目标对象2&#xff09;这样做的好处是…

数据结构--队列与循环队列

队列 队列是什么&#xff0c;先联想一下队&#xff0c;排队先来的人排前面先出&#xff0c;后来的人排后面后出&#xff1b;队列的性质也一样&#xff0c;先进队列的数据先出&#xff0c;后进队列的后出&#xff1b;就像图一的样子&#xff1a; 图1 如图1&#xff0c;1号元素是…

软件测试考试中的环路复杂度、线性无关路径的理解

题目如下&#xff0c;回答问题1至3 int GetMaxDay (int year ,int month){int maxday0; //1if (month>1 && month <12) { //2,3if (month2) { //4if (year %4 0 ) { //5if (year %100 0) { //6if (year %400 0) {//7maxday29; //8else //9maxda…

安防行业深度报告:技术创新与格局重构

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。通过运用云计算、人工智能等新技术&#xff0c;安防行业正经历从传统安防向 AI 智慧安防转型升级的过程。这种升级将改变传统安防只能事后查证、人工决策的劣势&#xff0c;使得全程监控、智能决策成为可能。通过运用后端云…

C/C++ 个人笔记

仅供个人复习&#xff0c; C语言IO占位符表 %d十进制整数(int)%ldlong%lldlong long%uunsigned int%o八进制整型%x十六进制整数/字符串地址%c单个字符%s字符串%ffloat&#xff0c;默认保留6位%lfdouble%e科学计数法%g根据大小自动选取f或e格式&#xff0c;去掉无效0 转义符表…

epoll() 多路复用 和 两种工作模式

1.epoll API 介绍 typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64; } epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */ };常见的Epoll检测事件&#xff1a;- EPOLLIN- EPOLLOUT- …

C#---第十九课:不同类型方法的执行顺序(new / virtual / common / override)

本文介绍不同类型的方法&#xff0c;在代码中的执行顺序问题&#xff1a; 构造方法普通方法&#xff08;暂用common代替&#xff09;、虚方法&#xff08;Virtual修饰&#xff09;、New方法&#xff08;new修饰&#xff09;三个优先级相同overide方法&#xff08;会替换virtual…

Arduino驱动TEMT6000传感器(光照传感器篇)

目录 1、传感器特性 2、硬件原理图 3、驱动程序 TEMT6000是一个三极管类型的光敏传感器,其光照强度和基极的电流成正比。用起来也相当简单,可以简单的连接该传感器的基极到模拟电压输入,通过简单的检测电压值就可以判断当前的光照强度。 1、

基于Linux操作系统的keepalived双机热备和keepalived+lvs(DR)基本配置操作

目录 keepalived双机热备 一、概述 &#xff08;一&#xff09;具体工作原理如下&#xff1a; &#xff08;二&#xff09;实验拓补 二、安装NFS、配置 1、第一台机器配置&#xff1a;NFS &#xff1a;192.168.11.101 2、更改配置文件 3、安装NFS进行目录共享 4、编辑…

花5分钟判断,你的Jmeter技能是大佬还是小白!

jmeter 这个工具既可以做接口的功能测试&#xff0c;也可以做自动化测试&#xff0c;还可以做性能测试&#xff0c;其主要用途就是用于性能测试。但是&#xff0c;有些公司和个人&#xff0c;就想用 jmeter 来做接口自动化测试。 你有没有想过呢&#xff1f; 下面我就给大家讲…

C# textBox1.Text=““与textBox1.Clear()的区别

一、区别 textbox.Text "" 和 textbox.Clear() 都可以用于清空文本框的内容&#xff0c;但它们之间有一些细微的区别。 textbox.Text "": 这种方式会将文本框的 Text 属性直接设置为空字符串。这样会立即清除文本框的内容&#xff0c;并将文本框显示为空…

git 基础入门

Git基础入门 Git是一个分布式 版本管理系统&#xff0c;用于跟踪文件的变化和协同开发。 版本管理&#xff1a;理解成档案馆&#xff0c;记录开发阶段各个版本 分布式&集中式 分布式每个人都有一个档案馆&#xff0c;集中式只有一个档案馆。分布式每人可以管理自己的档案…

ThinkPHP 验证码扩展库的使用,以及多应用模式下,如何自定义验证码校验规则

ThinkPHP 验证码扩展库的使用&#xff0c;以及多应用模式下&#xff0c;如何自定义验证码校验规则 一、安装二、页面使用三、验证码相关配置属性1. 自定义验证码配置2. 自定义验证码&#xff08;一&#xff09;普通验证码3. 自定义验证码&#xff08;二&#xff09;算数验证码4…

搭建web网站

1.基于域名www.openlab.com可以访问网站内容为welcome to openlab!!! (1).安装所需软件HTTPD、mod_ssl [rootserver ~]# yum install httpd mod_ssl -y 添加域名映射&#xff1a;vim /etc/hosts (2)创建网站目录及网页&#xff0c;修改主配置文件新建openlab目录网站 配置文…

第二届828 B2B企业节启动,华为云携手上万伙伴共筑企业应用一站购平台

当前&#xff0c;数字技术与实体经济深度融合&#xff0c;为千行百业注入新动力、拓展新空间。数据显示&#xff0c;2022年中国数字经济规模超过50万亿&#xff0c;占GDP比重超过40%&#xff0c;继续保持在10%的高位增长速度&#xff0c;成为稳定经济增长的关键动力。 为加速企…

DevOps系列文章之 Python基础

列表 Python中的列表类似于C语言中的数组的概念&#xff0c;列表由内部的元素组成&#xff0c;元素可以是任何对象 Python中的列表是可变的 简单的理解就是&#xff1a;被初始化的列表&#xff0c;可以通过列表的API接口对列表的元素进行增删改查 1、定义列表 1.可以将列表当成…

网络模型分析

# 用户空间和内核空间 # 阻塞IO # 非阻塞IO # IO多路复用 IO多路复用-select 内核中遍历找到就绪的fd并保留&#xff0c;不匹配的就置为0&#xff0c; 以上的操作重复&#xff0c;知道所有的FD都完成 IO多路复用-poll IO多路复用-epoll IO多路复用-事件通知机制 很多进程都要用…

WebGL非矩阵变换

目录 平移 示例代码&#xff1a; 齐次坐标矢量的最后一个分量w 旋转 p的坐标&#xff0c;可得等式 R1&#xff1a; 使用r、α、β来表示点p的坐标&#xff0c;可得等式 R2&#xff1a; 利用三角函数两角和公式&#xff0c;可得等式 R3&#xff1a; 最后&#xff0c;将…