数据结构实验--带环、相交链表问题

2023-11-01

一、问题描述:

基于课程上机关于单链表的作业,要求进一步实现以下需求:

  • 1.构造链表后,将元素值为 m 和 n(从键盘输入,如有多个相同元素值,仅考虑首个出现的元素)的节点建立连接,注意判断节点出现的先后关系,将后面出现的节点(假设为 n)的链域连到先出现的节点(假设为 m),将原 n 节点的后续节点搬迁到原单链表的头部,形成以下双头相交链表(如果使用带头结点的链表,搬迁过程中请自行额外增加一个头节点);
    在这里插入图片描述

  • 2.利用课程 ppt 中关于判断链表是否有环的方法,判断链表是否有环路,并求出环路出现的
    位置,即 m,n 节点的指针,请使用环路判断函数(输入为链表头指针),不可直接查找 m,n,
    支持使用 2 个不同的链表头部指针进行查询;

  • 3.将环路链接取消,恢复成单链表,并根据表头指针(输入)求解链表的中间节点(输出中
    间节点的值并返回指针),注意:不要将链表转换成数组来求解;

  • 4.编写函数,判断两个链表是否相交,函数输入为双头相交链表的两个头指针并输出相交的
    指针,如没有相交则返回 NULL。
    注意:判断相交是基于指针的比较(判断是否有相同的节点指针出现在两个链表中)而不是节点的值的比较。

二、需求分析:

首先要设计生成一个直链,然后考虑如何将其连接成符合题意的双头相交链表,设计判断环路的方法能够判断所给的链表是否有环并且返回环的入口,封装成函数供其他函数调用,最后就是判断链表相交,不应局限于题目所给条件而要充分考虑两链表相交的各种情况。

三、概要设计:

首先构建一个单链表,然后根据用户输入的m,n值依照题目要求的方式,构造符合要求的双头相交链表,并能够还原成原来的单链表。设计函数判断一个链表是否有环。编写函数判断给定的两个链表是否相交。

1、数据结构定义:

程序涉及到一个链表的储存,且涉及到的操作不多,故采用结构体的方式定义:

typedef struct LinkList
{   int Data=-999;
    LinkList* next=NULL;
}LinkList;//定义“链表”结构体,用于表示链表及相关节点
LinkList* CCListHead = new LinkList;//开始构建的单链表的头节点
LinkList* CCListHead2 = NULL;//对单链表处理过后的第二个头节点

2、模块设计:

1)main:显示菜单,并根据用户的选择做出判断,在其中调用其他函数完成功能
2)MakeLinklist():以上述已定义的头节点为头构建一个长为100的数值为随机数的单链表。
3)连接成环function1():将上述已经建立好的单链表依据要求连接成双头相交链表。
4)判断环路function2():利用快慢指针法,以判断上述节点为头的链表是否有环,并返回环的入口
5)取消环,并输出中间节点function3():用连接环时的相反的操作把环路取消,并根据输入的头节点输出对应的中间节点。
6)判断相交function4():判断已有的两个头节点所指示的链表是否相交。

3、各模块间的调用关系:

在这里插入图片描述

四、详细设计

主要算法设计:

1)直链的构建:

先构建一个没有重复数字的随机数数组

for (int i = 0; i <150 ; i++) {
        int Same = 1;
        A[i] = rand() / (double)RAND_MAX * (200);
        while (Same) {//直到没有重复数字才结束循环
            Same = 0;
            for (int j = 0; j < i; j++) {
                if (A[i] == A[j]) {
                    Same = 1;//表示有重复数字,需要重新生成随机数
                    A[i] = rand() / (double)RAND_MAX * (200);
                    break;
                }
            }
        }       
}

然后利用此数组中的各元素的值来构建链表

while(1)
    {
        Current->Data = A[i++];
        //Current->Data = i++;
        if (i == 100)break;
        Current->next = new LinkList;
        Current = Current->next;
    }

2)判断环路:利用快慢指针法判断环路

原理:设置一个快指针fast每次走两步即Fast = Fast->next->next;//快指针走两步,设置一个慢指针slow每次走一步即 Slow = Slow->next;//慢指针走一步;初始状态 两个指针都从头开始走,如果有环的话则由于两指针有速度差,则必定会在环中某点相遇,反之在Fast走到尾部之前,两指针都不会相遇,以此来判断给定的头指针所指示的链表是否有环。

LinkList* function2(LinkList* Head) {
    LinkList* Slow = Head;
    LinkList* Fast = Head;
    int isLoop = 0;
    while (Slow->next != NULL && Fast->next->next != NULL) 
    {
        Slow = Slow->next;//慢指针走一步
        Fast = Fast->next->next;//快指针走两步
        if (Slow == Fast) {//快慢相遇
            isLoop = 1;///则有环
            break;
        }
        if(Fast->next==NULL)//快指针到尾 则结束
            return NULL;
    }
    if (isLoop) {//有环的话 就判断环的入口
        Slow = Head;
        while (Slow != Fast) {
            Slow = Slow->next;
            Fast = Fast->next;
        }
        return Slow;
    }
    return NULL;
}

如果有环路,判断环入口的方法:第一次相遇点到入口的距离=头指针到连接点入口的距离,因此,分别从第一次相遇点、头指针head开始走,再次相遇的那个点就是连接点。

3)连接成环:将单链表按要求转化为双交头链表。

首先判断是否已经是双头相交链表,若是则返回错误提示信息,若不是则可以在给出的链表的100个数值里面挑选两个作为m n,自动判断先出现的为m,并按照题目所给的方法连接m、n得到要求的双头相交链表,并将新得到的链表头记为Head2.

步骤:

  • 先找到用户输入的m,n值对应的链表节点,小编号节点的指针记为MM大编号记为NN。
  • 然后对链表进行再次连接:NN后面的节点用Head2 指示,链表尾部节点由空改为指向MM,NN后继指向MM。即完成了对链表的再次连接
 CCListHead2 = NN->next;
    Tail->next = MM;
    NN->next = MM;

4)取消环并输出中间节点:

先判断头指针是否为空,有一个为空则无法恢复。
要恢复成单链表只需要找到四个位置:m的位置,n的位置,原链表尾的位置,原n后继的位置。
方法:m的位置只需要调用2即可返回入口即m的位置,n用Current指针遍历链表,并设置PreCurrent保存Current的前驱节点,当Current第二次经过m时的PreCurrent即为n的位置(因为第一次不在环中),原链表尾类似的方法遍历Head2(连接链表后制造出的头节点)第一次到m时对应的PreCurrent即为原链表尾记为tail,易判断Head2即为原n的后继。
判断完上述位置后,只需将n的后继改为Head2 指示的节点并让tail指向空即可。

if (CCListHead == NULL || CCListHead2 == NULL) {
        cout << "*****当前无法恢复成单链*****"<<endl;
        return ;
    }

    LinkList* Tail = NULL;
    LinkList* NN = NULL;

    LinkList* MM = function2(CCListHead);
    LinkList* Current = CCListHead2;
    LinkList* PreCurrent = new  LinkList;
    PreCurrent->next = Current;

    for (Current = CCListHead2; Current != MM; ) {
          PreCurrent = PreCurrent->next;
        Current = Current->next;
    }
    Tail = PreCurrent;

    Current = CCListHead;
    PreCurrent = new  LinkList;
    PreCurrent->next = CCListHead;

    int time = 0;//第几次到达MM
    while(time != 4)
    {
        PreCurrent = PreCurrent->next;
        Current = Current->next;
        if(Current == MM) time++;
    }
    NN = PreCurrent;
    NN->next = CCListHead2;
      
  
    CCListHead2 = NULL;
      Tail->next = NULL;

根据表头指针(输入)求解链表的中间节点:
读入用户输入的起点和终点后,遍历链表,遇到数据等于其中之一就开始输出节点的值,直到遇到用户输入的另外一个为止。

	int m, n;
    cout << "\n*****请输入要输出中间节点的范围 m,n*****" << endl;
    cin >> m >> n;

    int i = 0, MLoc = -1, NLoc = -1;
    for (Current = CCListHead; Current != NULL; Current = Current->next)
    {
        if (Current->Data == m) 
            MLoc = i;
        if (Current->Data == n) 
            NLoc = i;
        
        i++;
    }
    if (MLoc == -1 || NLoc == -1) {
        cout << "*****没有对应的两个节点******";
        return;
    }

    int start = 0;
    for (i = 0, Current = CCListHead; Current != NULL; Current = Current->next,i++) {
        if (i == min(MLoc, NLoc))start = 1;
        if (start) cout << Current->Data << "--";
        if (i == max(MLoc, NLoc))start = 0;
    }

5)判断相交:此问题分三种情况讨论,即:两直链、两环、环+直链。

情况如图所示: 在这里插入图片描述

首先,判断链表是否有环的方法已经在2给出,直接调用即可。

  • 两直链:判断两直链是否相交只需判断两直链尾指针是否一样即可,一样则说明两直链在尾部之前有相交。
  • 两环:两环相交在公共环的部分可能有一个或者两个入口,不管有几个,首先利用2给出的方法判断A链表环的入口,然后在B中遍历,判断A的入口是否在其中(注意带环链表的遍历,结束条件应该为第二次经过环入口),在的话则相交,进一步可以判断有一个还是两个入口。
  • 环+直链:此情况直接可判断为无相交。
 LinkList* Ahead=CCListHead;
    LinkList* Bhead=CCListHead2;
    LinkList* xx = function2(Ahead);
    LinkList* yy = function2(Bhead);

    LinkList* Current,PreCurrent;
    LinkList* AA,BB;

    if (xx==NULL&&yy==NULL) {
        //两个直链
        PreCurrent = new  LinkList;
        PreCurrent->next = Ahead;
        for (Current = Ahead; Current != NULL; ) {
            PreCurrent = PreCurrent->next;
            Current = Current->next;
        }
        AA = PreCurrent;
        PreCurrent = new  LinkList;
        PreCurrent->next = Bhead;
        for (Current = Bhead; Current != NULL; ) {
            PreCurrent = PreCurrent->next;
            Current = Current->next;
        }
        BB = PreCurrent;

        if(AA==BB){
            cout << "*****直链+直链,相交!!!*****";
        }
        else {
            cout << "*****直链+直链,不相交!!!*****";
            return NULL;
        }
    }
    else if (xx != NULL && yy != NULL) {
        //两个环
        LinkList* EntranceA;
        EntranceA= function2(Ahead);
        LinkList* EntranceB;
        EntranceB = function2(Ahead);

        int time = 0;//第几次到达MM
        int Jiao = 0;
        Current = Ahead;
        while (time != 4)
        {
            Current = Current->next;
            if (Current == EntranceA) time++;
            if (Current == EntranceB) {
                cout << "*****环+环,相交!!!*****";
                Jiao = 1;
                break;
            }
        }
        if(!Jiao)cout << "*****环+环,不相交!!!*****";
        return NULL;

    }
    else {
        cout << "*****直链+环,不相交!!!*****";
        return NULL;
    }

以上就是本程序的核心代码部分

五、用户手册:

打开程序后,程序出现菜单,选择你需要的功能,输入对应的序号按下回车即可,当选择功能一即连接成环时,程序会展示出预先生成的链表中的所有一百个值,选择两个值输入,系统自动判断先出现的为M反之为N,并连接成题目要求的链表,随后展示出连接的情况。当选择功能三时,如果已经存在相交链表则将其恢复成初始状态,随后展示出预先生成的链表中的所有一百个值,选择两个值输入后,程序会输出由用户输入的两个值对应节点的中间所有节点的值。其他功能类似,可反复使用。

六、调试报告:

测试过程中出现了以下几个问题:
恢复成直链失败:
尝试使用恢复成直链后,再次输出的链表与原链表不同,表明未正确恢复,于是将链表中的数据全部换成连续的一到一百,然后再次尝试就很容易发现出错的位置,分析后发现是N的位置找的不对,考虑到采用遍历的方法判断当前指针到达M则其后继即为N,但这是在环中的情况,在此之前还要经历在前半段直链的情况,所以用这种方法每次找到的都是在直链部分M的前驱而不是N。修改方法:用一个统计遍历次数的变量time初始化为零,每次当前指针遍历到M 时time就加一,这样只要time不是零,在当前指针遍历到M的时候其前驱就是N,修改过后程序即可输出预期的结果。
又尝试了以下改进:
对链表的相交情况进行可视化,即输出连接后的双头相交链表,具体采取的方法:
依次输出Head1到M,M到N,Head2到M即可表示出相交后的链表的具体情况,分析后发现与预期完全吻合。

八、测试结果:

测试结果如下:
在这里插入图片描述

图 1连接成环

在这里插入图片描述

图 2判断环路

在这里插入图片描述

图 3判断相交
在这里插入图片描述

图 4转化成直链并输出中间节点

在这里插入图片描述

图 5未连接状态下判断环
在这里插入图片描述

图 6 改进后的界面(输出链表的相交情况)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构实验--带环、相交链表问题 的相关文章

随机推荐

  • 如何实现百万并发连接的 Nginx 集群

    要实现百万并发连接的 Nginx 集群 可以考虑以下几种方案 横向扩展 使用多台 Nginx 服务器来处理并发连接 通过将流量分发到多个节点 每个节点处理一部分连接 从而实现并发连接的处理能力扩展 可以使用负载均衡器 如硬件负载均衡器 Ng
  • Anaconda安装教程(超详细)

    Anaconda安装教程 超详细 2022 11 16成功配置写下这篇文章 1 Anaconda的下载 我是在官网下载的 并没有网上说的那么慢 大概5 7分钟左右就下好了 这里附上官网下载地址 Anaconda官网下载地址 进去是这样的 直
  • alibaba/COLA 4.0框架 使用记录

    文章目录 背景 COLA框架 开发情况 出现的问题 总结 建议 背景 简介 开发团队之前没用过DDD开发 第一次用https github com alibaba COLA框架试着做项目 记录一些遇到的问题 https github com
  • 红黑树与平衡二叉树区别?

    如果说平衡二叉树是一个类的话 那么红黑树就是该类的一个实例 算法的书我丢久了 一下子也找不到 我是凭记忆说的 红黑树的算法比较麻烦 但它的思想很好 如果理解了它的思想也就理解它的算法 我也只记得思想 具体算法记不得了 我就在这说说思想吧 红
  • html5 图片 遮罩层,6种炫酷的鼠标滑过图片显示遮罩层特效

    这是一款使用jQuery和CSS3制作的炫酷的鼠标滑过图片显示遮罩层特效 该图片制作层特效共6种不同的效果 使用一些简单的jQuery代码和CSS3过渡效果来完成 简单实用 可以为网站图片添加非常不错的效果 制作方法 HTML结构 该图片遮
  • 亿图脑图MindMaster(Pro)

    下载地址 https www edrawsoft cn download 微信扫码登录 无限结点
  • QT交叉编译arm

    QT环境以及交叉编译环境的搭建 提示 这个操作比较常规 我就说一下自己遇到的一些问题然后一些注意事项 文章目录 QT环境以及交叉编译环境的搭建 前言 一 QT使用方面 先得知道QT是怎么回事 QT是什么和我认为的优势 干货来了 qmake
  • 虚拟地址 到底如何映射到 物理地址 的?

    一 背景 1 讲故事 我发现有很多的 NET程序员 写了很多年的代码都没弄清楚什么是 虚拟地址 更不用谈什么是 物理地址 以及Windows是如何实现地址映射的了 这一篇我们就来聊一聊这两者之间的联系 二 地址映射研究 1 找虚拟地址 怎么
  • html调用内网海康威视摄像头

    html调用内网海康威视摄像头 我的需求很简单就是在html的主页上用iframe加载出摄像头 海康威视的摄像头无法直接调用 必须安装海康提供的web插件包 使用插件的demo是可以调用的 但是单独搞出来又无法使用 所以直接在原来的demo
  • opencv ffmpeg推流

    基于opencv采集推流 1 opencv采集rtsp解码 可以基于usb 摄像机 调用系统驱动 和rtsp 调用ffmpeg 接口 转yuv加解码 摄像机 2 ffmpeg缩放转换像素格式 3 ffmpeg编码H264 4 ffmpeg推
  • 使用JS实现对页面的繁体简体翻译转换

    使用JS实现对页面的繁体简体翻译转换 效果图 一 HTML代码 二 Js代码 总结 效果图 废话少说直接上代码 一 HTML代码
  • wsl无法连接到win上的docker

    https docs docker com desktop windows wsl
  • QT 写一个属于自己的消息弹窗MessageBox

    前言 在接触公司的一个桌面应用项目后 发现里面很多窗体都是自己写的而不是使用QT自带的 例如消息弹窗 今天这篇博客就记录下来如何自己写一个消息弹窗 内容可能有点多 但都是本人自己一步一步操作后 测试可行后才记录下博客这里来的 希望对看到这篇
  • kaldi中SHELL调用C++程序过程源码分析

    引入 kaldi真正的核心源码 都是C 写成的 这个结论可以从如下两点得以确认 1 在kaldi的源码kaldi src目录下 能看到很多扩展名为 cc的源程序 这是linux下C 源码 2 在源码中 比如kaldi src featbin
  • 和导师的第二次探讨

    Jason提问 导师 最近读文献的方面我碰到两个问题 一 就是感觉读的太杂了 人工智能方向太大 文章五花八门 二 内容刚接触感觉晦涩难懂 特别是英文文献 而且用翻译软件意思有时候翻译成中文就感觉也不对 我应该如何解决呢 导师回答 对于问题一
  • sourceInsight官网介绍及插入定制语言支持

    sourceInsight官网介绍及插入定制语言支持 版本说明 版本 作者 日期 备注 0 1 ZY 2019 6 4 初稿 目录 文章目录 sourceInsight官网介绍及插入定制语言支持 版本说明 目录 一 sourceinsigh
  • 已经有了ERP,为什么还要上MES?

    当前 制造企业面临着巨大的竞争和成本压力 利润越来越少 交货时间要求越来越短 人力成本越来越高 产品越来越复杂 大多数企业已经在使用ERP系统了 他们会想 我已经上了ERP了 为什么还需要MES系统 许多工厂车间只有很有限的IT系统 比如自
  • Vector简介说明

    转自 Vector简介说明 下文笔者讲述Vector简介说明 如下所示 Vector简介 Vector集合和ArrayList集合功能相似 底层都是通过数组来实现集合的 Vector和ArrayList最大的区别是Vector的很多方法都是
  • 开发Android硬件访问服务

    在http blog csdn net getnextwindow article details 47731597中 为Android系统添加了HAL模块 开发好一个硬件抽象层以后 我们通常还需要在应用程序框架中实现一个硬件访问服务 硬件
  • 数据结构实验--带环、相交链表问题

    一 问题描述 基于课程上机关于单链表的作业 要求进一步实现以下需求 1 构造链表后 将元素值为 m 和 n 从键盘输入 如有多个相同元素值 仅考虑首个出现的元素 的节点建立连接 注意判断节点出现的先后关系 将后面出现的节点 假设为 n 的链