首页 > 编程语言 >C++数据结构实验题目解析

C++数据结构实验题目解析

时间:2024-11-11 22:18:37浏览次数:3  
标签:链表 解析 ListNode nullptr C++ next current 数据结构 节点

目录

题目:

考点分析:

难点1:就地逆置

步骤:

代码实现:

核心代码详细解释:

难点2:①非递减链表,②删除相同元素

代码详解①:

代码详解②:

完整代码:


大家好,今天我就来给大家分析一下我上期分享的题目的详细解析,编程的能力都是逐步提升的,但是思维的锻炼可以提前进行,这样有助于我们以后自己对着陌生的代码进行分析。废话不多说,我们现在开始。(完整代码在文章最后)

题目:

1.以单链表作为存储结构,实现线性表的就地逆置(提示,就地逆置:在不使用额外的数据结构或空间的情况下,将单链表中的节点顺序反转,使得原本指向下一个节点的指针指向了前一个节点。完成这一操作后,链表的第一个数据元素变为最后一个数据元素,而最后一个数据元素则成为第一个数据元素)。

2. 创建一个非递减序(有重复值)的单链表,实现删除值相同的多余结点。

考点分析:

基础:单链表的相关知识(单链表的存储结构,基本的用法)

重点:链表的就地逆置,非递减链表,删除相同元素。

这里的基础我们就不再赘述,由于篇幅原因我们就主要讲一讲重点。

难点1:就地逆置

使链表的元素可以在存储空间内进行顺序替换。

步骤:
  1. 初始化三个指针,一个指向链表的头节点(但不包括头节点,仅作为遍历的起点),一个初始化为空(用于构建新的链表头),另一个用于遍历链表。
  2. 遍历链表,对于每个遍历到的节点:
    • 将当前节点的下一个节点保存起来,以便在遍历结束后能够继续遍历。
    • 将当前节点的指针指向新的链表头(即之前为空的那个指针所指向的位置)。
    • 更新新的链表头为当前节点。
  3. 当遍历完成后,新的链表头即为逆置后的链表的头节点。

这种方法通过迭代的方式,逐个将链表节点从原链表中“删除”并插入到新的链表中,最终实现了链表的逆置。

代码实现:

就地逆置的函数

// 就地逆置链表
    void reverseList() 
    {
        ListNode* prev = nullptr;
        ListNode* current = head;
        ListNode* next = nullptr;
        while (current!= nullptr) 
        {
            next = current->next;
            current->next = prev;
            prev = current;
            current = next;
        }
        head = prev;
    }

大家看到上面的代码有没有一种熟悉的感觉。没错核心思想还是变量的交换问题。 

首先我们先定义两个空指针,定义一个指针指向头节点。由于就地逆置需要将整个链表都进行操作,所以我们需要一个循环,循环条件就是我们的current指针不为空(逆置会使我们的头指针变成尾指针,所以这就是第一个关键点,循环条件的确立)。核心步骤:首先我们先将next指向current的next域;next指向current的next域赋值为prev指针;然后prev指针指向current指针指向的地址;current指针指向next指针指向的地址。最后循环外我们将头指针指向prev指针。

核心代码详细解释:
  1. 定义指针变量
    • ListNode* prev = nullptr;:定义一个指针prev,初始化为nullptr,用于记录当前节点的前一个节点。在反转过程中,prev将指向当前已反转部分的最后一个节点。
    • ListNode* current = head;:定义一个指针current,初始化为链表的头节点head,用于遍历链表。
    • ListNode* next = nullptr;:定义一个指针next,初始化为nullptr,用于暂存current节点的下一个节点,因为在反转current节点的指针之前需要知道它的下一个节点是什么。
  2. 遍历链表并反转
    • while (current != nullptr):当current不为nullptr时,继续循环。这意味着只要还没有到达链表的末尾(即current不是nullptr),就继续反转过程。
  3. 反转当前节点的指针
    • next = current->next;:首先,保存current节点的下一个节点到next
    • current->next = prev;:然后,将current节点的next指针指向前一个节点prev,完成当前节点的反转。
  4. 移动指针
    • prev = current;:将prev移动到当前节点,因为当前节点现在已经是反转后链表的最后一个节点。
    • current = next;:将current移动到下一个待反转的节点(即之前保存的next)。
  5. 更新头节点
    • head = prev;:当current变为nullptr,表示已经遍历并反转了整个链表。此时,prev指向的是新链表的头节点(即原链表的尾节点),因此更新headprev

总结
这段代码通过三个指针prevcurrentnext,在不使用额外存储空间的情况下,实现了单链表的原地反转。遍历链表时,逐个节点地将它们的指针指向前一个节点,最终将链表的头节点更新为反转后的新头节点。


难点2:①非递减链表,②删除相同元素

首先就是第一个函数:非递减链表:

代码详解①:
  1. 创建新节点
    • ListNode* newNode = new ListNode(val);:首先,创建一个新的ListNode节点,并用传入的val值初始化它的数据部分。
  2. 处理特殊情况
    • if (head == nullptr || val <= head->data):检查链表是否为空(head == nullptr)或者新节点的值小于或等于头节点的值。如果是这两种情况之一,说明新节点应该被插入到链表的开头。
      • newNode->next = head;:将新节点的next指针指向当前的头节点。
      • head = newNode;:更新头指针,使其指向新节点。
  3. 在链表中间或末尾插入节点
    • 如果新节点的值大于头节点的值,并且链表不为空,那么需要找到新节点应该插入的位置。
    • ListNode* current = head;:定义一个指针current,初始化为链表的头节点,用于遍历链表。
    • while (current->next != nullptr && current->next->data < val):遍历链表,直到找到第一个其next节点的值大于或等于新节点值的节点。这意味着新节点应该被插入到这个节点之后。在这个循环中,current指针会移动到链表中值小于新节点值的最后一个节点。
    • newNode->next = current->next;:将新节点的next指针指向current节点的下一个节点。
    • current->next = newNode;:将current节点的next指针指向新节点,完成插入操作。

总结
这个函数通过遍历链表,找到一个合适的位置来插入新节点,以保持链表的非递减顺序。如果链表为空或新节点的值小于或等于头节点的值,新节点会被插入到链表的开头。否则,新节点会被插入到链表中第一个值大于或等于它的节点之前。这样,链表在插入新节点后仍然保持非递减顺序。

 // 插入节点以保持非递减序
    void insertInOrder(int val) 
    {
        ListNode* newNode = new ListNode(val);
        if (head == nullptr || val <= head->data)
    {
            newNode->next = head;
            head = newNode;
    } 
        
        else 
        {
            ListNode* current = head;

            while (current->next!= nullptr && current->next->data < val) 
            {
                current = current->next;
            }

            newNode->next = current->next;
            current->next = newNode;
        }
    }

这是第二个函数:删除相同的多余节点;

代码详解②:

这段代码是一个用于删除单链表中值相同的多余节点的函数。它遍历链表,比较当前节点和下一个节点的值,如果它们相同,则删除下一个节点(即多余节点)。下面是代码的详细分析:

  1. 初始化
    • ListNode* current = head;:定义一个指针current,初始化为链表的头节点head。这个指针将用于遍历链表。
  2. 遍历链表
    • while (current != nullptr && current->next != nullptr):这个循环会持续进行,直到current指针到达链表的末尾(current == nullptr)或者current是链表的最后一个节点(current->next == nullptr)。由于我们需要比较当前节点和下一个节点的值,所以必须确保current->next不是nullptr
  3. 检查并删除重复节点
    • if (current->data == current->next->data):如果当前节点的值等于下一个节点的值,说明找到了一个重复节点(在这个上下文中,“重复”意味着相邻节点的值相同)。
      • ListNode* temp = current->next;:定义一个临时指针temp,指向要删除的重复节点(即current的下一个节点)。
      • current->next = current->next->next;:将currentnext指针跳过temp节点,直接指向temp的下一个节点。这样就从链表中移除了temp节点。
      • delete temp;:释放temp节点所占用的内存。这是很重要的,因为C++中动态分配的内存如果不手动释放,会导致内存泄漏。
  4. 移动到下一个节点
    • else { current = current->next; }:如果当前节点的值不等于下一个节点的值,说明没有重复,那么current指针应该移动到下一个节点,继续遍历链表。

注意事项

  • 这个函数只删除了相邻的重复节点。如果链表中有非相邻的重复节点(例如,1 -> 2 -> 3 -> 1 -> 4),这些重复节点将不会被删除。
  • 函数正确地处理了内存释放,避免了内存泄漏。
  • 函数的时间复杂度是O(n),其中n是链表中节点的数量,因为每个节点都被访问了一次。
  • 函数的空间复杂度是O(1),因为它只使用了有限数量的额外变量(currenttemp),不随链表长度变化。
// 删除值相同的多余节点
    void removeDuplicates() 
    {
        ListNode* current = head;

        while (current!= nullptr && current->next!= nullptr)     
    {
            if (current->data == current->next->data)
             {
                ListNode* temp = current->next;
                current->next = current->next->next;
                delete temp;
             } 
            else 
            {
                current = current->next;
            }
    }

    }

标签:链表,解析,ListNode,nullptr,C++,next,current,数据结构,节点
From: https://blog.csdn.net/2301_81280642/article/details/143689967

相关文章

  • C++中需要资源释放的变量
    资源或变量需要释放的情况通常是在其内存或其他系统资源是动态分配的或非自动管理的,尤其是在手动分配资源时(如new、malloc、文件句柄、网络连接等)。未释放这些资源会导致内存泄漏或资源泄漏。以下是一些典型需要释放资源的场景:1.动态内存分配通过new、new[]、malloc、calloc......
  • 用C++写数字直角三角形和摘苹果问题
    题目描述给出n,请输出一个直角边长度是 n的数字直角三角形。所有数字都是2位组成的,如果没有2位则加上前0。输入格式输入一个正整数n。输出格式输出如题目要求的数字直角三角形。输入输出样例输入#1复制5输出#1复制010203040506070809101112131415说明......
  • C向C++过渡篇(三)
    ----------cin和coutcin的作用类似C语言中的scanfcout的作用类似C语言中的printf区别:cin和cout不是函数,是C++中用来进行输入和输出的一个对象使用时,不需要去指定格式符(%d,%c,%f之类的),在使用时,要包含头文#include<iostream>cin和cout可以理解为变量,它们是存在于一个叫做......
  • C++ lower_bound 函数用法
    C++lower_bound函数用法因为文本块不支持下划线,所以以下均打成\(\text{lower-bound}\)。虽然只是简单语法,但是我确实不太能记住。比如很多分块题要求在整块二分,此时如果能善用\(\text{lower-bound}\)函数就能少写一个二分。然后本文只是作者自己看源代码理解的,当然是有不......
  • 郝玩的数据结构1——单调栈,单调队列
    栈和队列是很郝咏的Stl,那么,我们手搓——用数组模拟故事背景:辣鸡老o在学单调栈&单调队列——我栈top为栈顶,易得出出栈即top--,入栈++top=(ovo)......——完全不会讲,那么上马:点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=114514;intstk[N],top=0;......
  • C向C++过渡篇(一)
    ----------bool类型:c++独有,这是一种数据类型,用来描述“真”或“假“用sizeof(bool)来求bool类型变量在内存中占多少个字节的内存,得出,bool类型在内存中占用一个字节取值范围:只有两个值:turn(真的),false(假的)bool,可以给它赋值别的值,遵循“非0为真”原则----------内联函数......
  • 力扣 170. 两数之和 III - 数据结构设计 two-sum III
    数组系列力扣数据结构之数组-00-概览力扣.53最大子数组和maximum-subarray力扣.128最长连续序列longest-consecutive-sequence力扣.1两数之和N种解法two-sum力扣.167两数之和IItwo-sum-ii力扣.170两数之和IIItwo-sum-iii力扣.653两数之和IVtwo-sum-IV力......
  • C++【深入项目-检测键盘】
    神马是检测键盘,就是让编辑器可以检测键盘按下了什么按键,我们先科普复习检测键盘 。检测键盘需要用到一些函数,请见下:!KEY_DOWN(80)这个代码是检测按下键盘上P按键。那80是什么?原来是对应按键的,不只有数字表示,还有字母表示:说明BackSpaceBackSpace8TabTab9Clear12En......
  • c++ 对于传递引用和传递值的理解
    首先先上一段c++代码,可以看出foo函数参数是引用类型,bar函数参数是值类型typedefstructA{intx;inty;}A;voidfoo(A&a){ra.x++;}voidbar(Aa){a.x++;}intmain(){Aa={1,2};foo(a);bar(a);return0;}在vscode......
  • WEB 漏洞 - SQL 注入之 MySQL 注入深度解析
    目录WEB漏洞-SQL注入之MySQL注入深度解析一、从宇宙奇想到SQL注入二、SQL注入原理回顾(一)基本概念(二)以简单PHP代码示例说明三、MySQL注入步骤(一)确定注入点(二)判断注入类型(三)利用注入获取信息或执行恶意操作四、防御MySQL注入的方法(一)使用参数化查询(二)......