【卡码网C++基础课 13.链表的基础操作1】

news2024/9/20 9:26:41

目录

  • 题目描述与分析
  • 一、指针
  • 二、链表
  • 三、定义链表节点
  • 四、链表的插入
  • 五、代码编写


题目描述与分析

题目描述:
构建一个单向链表,链表中包含一组整数数据。输出链表中的所有元素。
要求:
1.使用自定义的链表数据结构
2.提供一个 linkedList 类来管理链表,包含构建链表和输出链表元素的方法
3.在 main 函数中,创建一个包含一组整数数据的链表,然后调用链表的输出方法将所有元素打印出来

输入描述:
包含多组测试数据,输入直到文件尾结束。
每组的第一行包含一个整数 n,表示需要构建的链表的长度。
接下来一行包含 n 个整数,表示链表中的元素。

输出描述:
每组测试数据输出占一行。
按照顺序打印出链表中的元素,每个元素后面跟一个空格。

输入示例:

5
1 2 3 4 5
6
3 4 5 6 7 8

输出示例:

1 2 3 4 5
3 4 5 6 7 8

在之前的学习中,我们接触到了字符串和数组这两种结构,它们具有着以下的共同点:
1.元素按照一定的顺序来排列
2.可以通过索引来访问数组中的元素和字符串中的字符

但是它们也都有着一些缺点:
1.固定大小:数组的大小通常是固定的,一旦分配了内存空间,就难以动态地扩展或缩小,如果需要存储的元素数量超出了数组的大小,就需要重新分配更大的数组,并将原来数组的内容复制过去,需要执行很多额外的操作。
2.内存是连续的:正是因为元素按照一定的顺序来排列,它们在计算机内存中的存储也是连续的,这也就意味着,当需要存储一些需要占用空间较大的内容,也只能找一些大块的内存区域,而空间比较小的内存区域就被浪费了,从而导致了内存资源浪费。
3.固定的数据类型:数组要求所有元素具有相同的数据类型,字符串存储的都是字符,如果需要存储不同类型的数据,数组和字符串就显得无能为力了。

还有重要的一点是,如果我们想要往数组中新增加或者删除一个元素,会特别麻烦!
比如下面的图例,想要往数组中删除第三个元素,当完成删除后,还需要从删除元素位置遍历到最后一个元素位置,分别将它们都向前移动一个位置,也就是说后续的所有元素都要改变自己的位置,这是十分耗时的操作。
在这里插入图片描述
那有没有什么数据结构能够解决上面的问题呢?
那就是我们这节课中将要学习到的链表!

一、指针

在 C++ 中,指针是基本的数据类型,用于存储变量的内存地址。通过指针,你可以直接访问和操作存储在内存中的数据。

声明指针: 指针的声明需要指定指针指向的数据类型,以便解引用时知道内存中应读取多少字节以及如何解释它。

int *ptr; // 声明一个指向整数的指针
// 也可以这样写
int* ptr;

初始化指针: 指针应当被初始化为某个变量的地址,使用取地址符 &。

int var = 10;
int* p = &var;  // p 现在指向 var

使用指针:
1.解引用(使用 * 运算符)允许你访问指针指向的内存地址中的数据。
2.指针算术,比如增加指针(指针移动到下一个数据元素)。

int value = *p;  // 读取 p 指向地址中的值,此处为 10
*p = 20;         // 修改 p 指向的地址中存储的值,var 现在为 20

指针类型:
1.指向基本数据类型的指针:如 int*, char* 等。
2.指向数组的指针:可以使用指针来遍历数组。
3.指向指针的指针(多级指针):例如,int** 是一个指向 int* 的指针。
4.指向函数的指针:可以用来存储函数的地址,用于回调函数等。

指针与数组密切相关。数组名本质上是指向数组第一个元素的指针。

int arr[3] = {10, 20, 30};
int* ptr = arr;  // 指向数组的第一个元素
for (int i = 0; i < 3; ++i) {
    cout << *(ptr + i) << " ";  // 输出: 10 20 30
}

指针还可以执行加法、减法等算术操作,以访问内存中的不同位置。

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 指向数组的第一个元素
int value = *(ptr + 2); // 获取数组的第三个元素(值为3)

除此之外,还有一个特殊的空指针值,通常表示为nullptr,用于表示指针不指向任何有效的内存地址。

int *ptr = nullptr; // 初始化为空指针

二、链表

链表是一种常用的数据结构,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。链表提供了在任何位置快速插入和删除节点的能力,这使得它在需要频繁修改的场景下非常有用,例如实现队列、栈和其他复杂的数据结构。
在这里插入图片描述
链表的第一个节点的存储位置被称为头指针,然后通过next指针域找到下一个节点,直到找到最后一个节点,最后一个节点的next指针域并不存在,也就是“空”的,在C++中,用null来表示这个空指针。

为了简化链表的插入和删除操作,我们经常在链表的第一个节点前添加一个节点,称为虚拟头节点(dummyNode),头节点的数据域可以是空的,但是指针域指向第一个节点的指针。

头指针是链表指向第一个节点的指针,访问链表的入口,经常使用头指针表示链表,头指针是链表必须的

头节点是为了方便操作添加的,不存储实际数据,头节点不一定是链表必须的

链表的类型
单向链表:每个节点只包含一个指向下一个节点的指针。
双向链表:每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。
循环链表:链表的尾部不是指向 nullptr,而是指回链表的头部。

基本操作
链表的基本操作通常包括插入节点、删除节点和遍历链表:
插入节点:可以在链表的头部、尾部或任何指定节点后插入新节点。
删除节点:可以删除链表中的任何节点,需要调整前一个节点的指针以绕过被删除的节点。
遍历链表:从头节点开始,通过节点中的指针移动到链表的尾部。

三、定义链表节点

传统的定义变量的方式只能使用一种数据类型,无法处理链表这种既包含数据域名、又包含指针域的复合结构,这就需要使用到struct结构体,结构体是一种用户自定义的数据类型,比如想要定义一个Person的结构体

// Person结构体
struct Person {
  // 使用 数据类型 成员变量的形式来定义
  int age; // int类型的年龄
  std::string name; // string类型的名字
}

结构体可以组合多个不同类型的成员变量,成员变量可以是各种数据类型,包括整数、浮点数、字符串、其他结构体等,所以你可以根据需要定义自己的结构体来组织数据。

// 链表节点结构体
struct ListNode {
    int val;  // 存储节点的数据
    ListNode *next; // 下一个节点也是链表节点,所以也是ListNode类型,*表示指针(地址),next是名称
}

但结构体只是个“模具”,创建的Person结构体虽然具有age、name,但它只是一个Person的概念,无法表示具体的人,只有将其“初始化”,比如"张三,18", “李四、20”,才能真正的使用。

初始化结构体的方式有很多,这里我们使用构造函数的方式来进行,构造函数的名称与结构体的名称相同,和其他函数不一样的是,构造函数没有返回类型,除此之外类似于其他的函数,构造函数也有一个(可能为空)的参数列表和一个函数体(可能为空)。链表结构体的构造函数代码如下:

 ListNode(int x) : val(x), next(nullptr) {}

这里的ListNode(int x)表示定义一个接收整数参数 x的名称为ListNode的构造函数(名称和结构体相同),:表示初始化列表的开始,val(x)表示链表数据域的值被初始化为传递的参数 x ,next(nullptr)则表示next指针被初始化为nullptr,表示没有下一个节点。

下面的完整代码定义了一个名为ListNode的结构体,用于表示链表中的一个节点,包含存储节点数据的数据域和存储下一个节点地址的指针域。

// 链表节点结构体
struct ListNode {
    int val;  // 存储节点的数据
    ListNode *next; // 指向下一个节点的指针	
  // 构造函数,用于初始化节点, x接收数据作为数据域,next(nullptr)表示next指针为空
    ListNode(int x) : val(x), next(nullptr) {}
};

四、链表的插入

上面我们完成了定义链表节点的操作,那应该完成怎样的操作将链表节点插入到链表的尾端,从而形成一个完整的链表呢?至少应该包括以下操作:

创建一个新的链表节点,初始化它的值为val;
将新的节点放入到链表的尾部,接入链表,也就是当前链表的尾部的next指向新节点;
接入的链表节点变为链表的尾部。

假设我们用cur来表示当前链表的尾节点
在这里插入图片描述
上面的操作用代码来表示如下:

ListNode *newNode = new ListNode(val); // 通过new构造一个新的节点,节点的值为val
cur -> next = newNode; // cur节点的next节点是新节点,从而将新节点接入链表
cur = cur -> next;      // 新插入的节点变更为新的尾节点,即cur发生了变更

这里有两个新的语法:new运算符和箭头语法->

new是一个运算符,它的作用就是在堆内存中动态分配内存空间,并返回分配内存的地址,使用方式一般为指针变量 = new 数据类型, 比如下面的代码

int *arr = new int[5]; // 分配一个包含5个整数的数组的内存空间,并返回一个地址,指针arr指向这个地址

箭头语法(->):用于通过指针访问指针所指向的对象的成员,cur 是一个指向 ListNode 结构体对象的指针,而 next 是 ListNode 结构体内部的一个成员变量(指向下一个节点的指针)。使用 cur->next 表示访问 cur 所指向的节点的 next 成员变量。

五、代码编写

按照题目要求,先把基础的代码结构给写出来

#include <iostream>
using namespace std;
// 定义一个链表节点
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};
int main() { 
    
}

一般都会为链表创建一个虚拟头节点

ListNode *dummyHead = new ListNode(0); // 定义了虚拟头结点,dummyNode指向它的地址

之后我们需要根据输入的值构建链表,每组的第一行包含一个整数 n,表示需要构建的链表的长度。

while(cin >> n) {
  
}

可以定义一个指向当前节点的指针 cur,刚开始指向虚拟头结点。

ListNode *cur = dummyHead; // 指针cur指向虚拟头节点

构建一个链表需要以下几步:
接收输入的值,并根据读取的值val创建一个新的链表节点,初始化它的值也为val;
将新的节点放入到链表的尾部,接入链表,也就是当前链表的尾部的next指向新节点;
新接入的链表节点变为链表的尾部;

for (int i = 0; i < n; i++) { // 或者使用while(n--)
    cin >> val; // 输入链表节点的val值
    ListNode *newNode = new ListNode(val); // 根据val值构造一个新的节点
    cur -> next = newNode; // 当前指针的下一个节点为新节点,从而将新节点接入链表
    cur = cur -> next;      // cur 指向下一个节点(也就是新创建的节点)
}

当输入结束时,链表也就构建完成了,想要输出链表的节点需要从头开始重新遍历, 所以需要将cur重新指向链表的虚拟头节点,循环输出链表可以使用while循环,但是什么时候才可以退出循环呢?

那就是当cur指向最后一个节点的时候,此时cur->next是空指针,也就是说只要cur->next != null就可以一直循环下去,直到cur->next == null退出循环,在循环过程中需要做两件事情:
输出链表节点的值
将cur指向下一个节点

  // 此时构造链表完毕,输出链表节点需要从头遍历
cur = dummyHead;
// 只要cur->next != NULL,说明链表还没有遍历完
while (cur->next != NULL) {
  // 输入cur的next指针的val值
    cout << cur->next->val << " ";
  // 将cur指向下一个节点
    cur = cur -> next;
}
cout << endl;

链表输出完成后,输出一个换行符,换行输出下一组链表。

cout << endl;

完整代码如下:

#include <iostream>
using namespace std;
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};
int main() {
    int n, val;
    ListNode *dummyHead = new ListNode(0); // 定义了虚拟头结点
    while (cin >> n) {
        ListNode *cur = dummyHead; // 定义一个临时变量来构建链表
        for (int i = 0; i < n; i++) { // 或者使用while(n--)
            cin >> val;
            ListNode *newNode = new ListNode(val); // 根据读取的值 val 创建一个新的链表节点,并初始化它的值为 val
            cur -> next = newNode; // 将新节点接入链表
            cur = cur -> next;      // cur 指向下一个节点
        }
        cur = dummyHead;
      // 遍历链表节点并逐个输出
        while (cur->next != NULL) {
            cout << cur->next->val << " ";
            cur = cur -> next;
        }
        cout << endl;
    }
}

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

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

相关文章

一篇精通Ansible之playbook

华子目录 前言playbook概念playbook结构理解playbook核心元素playbook特点与优势 playbook基本语法ansible-playbook命令ansible总结查看主机清单ansible配置文件单个play语法检测运行 多个play查看模块doc变量事实变量导入变量文件字典变量 机密数据管理在playbook中导入变量文…

PTA团体程序设计天梯赛

这次题目出得比前几次简单很多&#xff0c;但有几道题占用的时间太多&#xff0c;导致后面几题仓促写完&#xff0c;未能全部正确&#xff0c;还是得多练 目录 L1-2 九牛一毛 L1-3 小孩子才做选择&#xff0c;大人全都要 L1-5 试试手气 L1-6 打PTA L1-8 随机输一次 L2-…

QT5.15.2加载mysql驱动-QMYSQL driver not loaded解决方法

Available drivers: "QSQLITE" "QODBC" "QODBC3" "QPSQL" "QPSQL7" QSqlDatabase: QMYSQL driver not loaded QSqlDatabase: available drivers: QSQLITE QODBC QODBC3 QPSQL QPSQL7 源码下载&#xff08;若存在&#xff0…

029集—CAD VBA识别“Esc”退出键——vba代码实现

vba程序运行时我们想按下“Esc”键时退出程序或做出进一步相应&#xff0c; 此时可借助windows API函数实现。 见下图&#xff1a; 部分代码如下&#xff1a; #If VBA7 Then 64位系统声明Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)…

【C++从练气到飞升】16---二叉搜索树

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、二叉搜索树概念 二、二叉搜索树的操作 2.1 二叉搜索树的查找 2.2 二叉搜索树的插入 2.3 二叉…

Linux基础 - yum、rzsz、vim 使用与配置、gcc/g++的详细解说

目录 一、Linux 软件包管理器 yum A.什么是软件包&#xff1f; B.关于rzsz&#xff0c;yum的配置 1.安装 sz&#xff0c;rz 命令&#xff1a; a.执行命令sz可将linux中的文件传输到Windows中 b.执行rz命令可将Windows中的文件传输到linux 2.scp XXX.tgz 用户名另一台lin…

BCLinux Euler 21.10 安装mysql 8.0.37 (二进制安装)

下载mysql安装包 #根据ldd --version的信息&#xff0c; 下载的是glic 2.28的包。 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 包名&#xff1a;mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz#root用户操作 #系统环境&#xff1a;BigCloud Enterpri…

注册安全分析报告:助通信息

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

Python操作ES

代码说明&#xff1a; 连接 Elasticsearch&#xff1a;使用 basic_auth 参数进行认证。测试连接&#xff1a;获取集群的健康状态&#xff0c;并格式化输出结果。索引文档&#xff1a;将一个文档索引到指定的索引中&#xff0c;并格式化输出结果。搜索文档&#xff1a;在指定的…

【python计算机视觉编程——2.局部图像描述子】

python计算机视觉编程——2.局部图像描述子 2.局部图像描述子2.1 Harris角点检测器在图像间寻找对应点 2.2 SIFT&#xff08;尺度不变特征变换&#xff09;2.2.3 检测兴趣点2.2.4 匹配描述子 2.3 匹配地理标记图像 2.局部图像描述子 2.1 Harris角点检测器 算法步骤 计算图像梯…

JS New Worker() 深度解析

JS New Worker() 深度解析 文章目录 一、New Worker() 是什么及为什么出现二、JS中如何使用 New Worker()1. 创建 Worker 线程2. 向 Worker 发送消息3. 接收 Worker 的消息4. 监听错误和结束事件5. 终止 Worker 三、Worker 包含哪些属性或方法 API1. 属性2. 方法 四、扩展与高级…

customRef 与 ref

ref() 我们已经很熟悉了&#xff0c;就是用来定义响应式数据的&#xff0c;其底层原理还是通过 Object.defineprotpty 中的 get 实现收集依赖( trackRefValue 函数收集)&#xff0c;通过 set 实现分发依赖通知更新( triggerRefValue 函数分发 )。我们看看 ref 的源码就知道了 …

适合学生党用的充电宝有哪些?四款百元性价比充电宝推荐

在如今这个电子设备不离手的时代&#xff0c;充电宝成为了学生党们的必备好物。无论是在教室、图书馆学习&#xff0c;还是外出游玩&#xff0c;一款可靠的充电宝能够为手机、平板等设备随时补充电量&#xff0c;让你不再为电量焦虑而烦恼。今天&#xff0c;我们就为学生党们精…

AES对称加密算法

1. 简介 AES是一种对称加密算法, 它有3种类型: AES-128: 密钥为128位(16字节)的AES, 加密10轮AES-192: 密钥为192位(24字节)的AES, 加密12轮AES-256: 密钥为256位(32字节)的AES, 加密14轮 密钥长度越长, 加密的强度越大, 当然与此同时开销也越大。每种类型下都有几种操作模式…

【JavaEE】深入浅出 Spring AOP:概念、实现与原理解析

目录 Spring AOPAOP概述Spring AOP快速⼊⻔引⼊AOP依赖编写AOP程序 Spring AOP 详解Spring AOP核⼼概念切点(Pointcut)连接点(Join Point)通知(Advice)切⾯(Aspect) 通知类型PointCut切⾯优先级 Order切点表达式execution表达式annotation⾃定义注解 MyAspect切⾯类添加⾃定义注…

力扣第71题:简化路径 放弃栈模拟,选择数据流√(C++)

目录 题目 思路 解题过程 复杂度 Code 题目 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中&#xff0c;一个点&#xff…

K8S持久化存储数据volumeMountsvolumes

环境&#xff1a; Ubuntu-1:192.168.114.110作为主 Ubuntu-2:192.168.114.120作为从1&#xff0c;node节点1 Ubuntu-3:192.168.114.130作为从2&#xff0c;node节点2 持久化volumeMounts pod里面&#xff1a;emptyDir和hostPath。存储在node&#xff0c;NFS...&#xff0c;Clo…

文本处理函数

1.文本的提取 left mid right 2.文本的查找与替换 replace&#xff0c;substitute 3.字符个数 len字符 lenb字节, office365好像没有此功能 4.数据的清理 clean , trim 5.找不同 exact

codetop标签动态规划大全C++讲解(上)!!动态规划刷穿地心!!学吐了家人们o(╥﹏╥)o

主要供自己回顾学习&#xff0c;会持续更新&#xff0c;题源codetop动态规划近半年 1.零钱兑换2.零钱兑换II3.面试题08.11.硬币4.单词拆分5.最长递增子序列6.最长递增子序列的个数7.得到山形数组的最少删除次数8.最长公共子序列9.最长重复子数组10.最长等差数列11.最大子数组和…

智能优化算法-海鸥优化算法(SOA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍&#xff1a; 海鸥优化算法 (Seagull Optimization Algorithm, SOA) 是一种基于群体智能的元启发式优化算法&#xff0c;它模拟了海鸥的觅食、飞行和社会交互行为&#xff0c;用于解决复杂的优化问题。 SOA的工…