链表的插入操作——CSP-J1真题详解

news2024/11/14 22:01:10

【题目】

假设有一个链表的节点定义如下:

struct Node {
int data;
Node* next;
};

现在有一个指向链表头部的指针:Node* head。如果想要在链表中插入一个新的节点,其成员 data 的值为 42,并使新节点成为链表的第一个节点,下面哪个操作是正确的?(   )

A. Node* newNode = new Node; newNode->data = 42; newNode->next = head; head = newNode;

B. Node* newNode = new Node; head->data = 42; newNode->next = head; head = newNode;

C. Node* newNode = new Node; newNode->data = 42; head->next = newNode;

D. Node* newNode = new Node; newNode->data = 42; newNode->next = head;

【答案】

A

【解析】

一、链表与数组的区别及其存在意义

假设有一群盲人去看电影,为防走散,它们必须排成一队。如果能订到连在一起的座位最好,工作人员只需将领头的那个带到第一个座位,后面一个接一个摸过去即可。这种靠紧挨在一起找座的方式就是数组。

这种订座方式虽然方便,但也有缺陷:需要预留足够多的可以挨着坐的空座位。留多少空位合适就成了问题。留少了,人不够坐;留多了,浪费座位。如果看电影的盲人数是不固定的,这种方式根本不可行。

解决的办法只能是哪有空位订哪个,结果订的座位就会变成东一个西一个,对于盲人来说,找座就是一件麻烦的事。工作人员可以把订好的座位依次用链子连在一起,然后同样将领头的带到第一个座位,后面一个接一个顺着链子摸过去即可,这种靠链子找座的方式就是链表。

链表就是用一条条链子依次将各种东西连成一串,它们整体看起来就像一个列表一样。

与数组相同的是,它们的数据都是有顺序的,像排队一样,因此都属于线性表。

与数组不同的是,链表在内存中是分散存储的,它分的是一小块一小块的地,用多少分多少,随时用随时分;数组是连续存储的,它是一次分一大片地,估摸着用多少就分多少。同样要存100条数据,数组是一次分配足够的内存,链表是分100次一项一项地分配内存。

数组要访问数据只需要知道是第几项即可;链表要访问数据得从头捋着链子一点一点向后爬。

数组要插入或删除数据的话比较麻烦,需要将元素进行整体移动;链表要插入、删除数据不涉及任何数据移动,只需要重新拴一下链子。

因此,数组的读取速度比链表快,链表的插入和删除速度比数组快。

二、链表的结构

要靠链条找座位,每个座位必须有两样东西,一个就是要坐的人,一个就链子。

同样地,对于链表,每一项称为一个节点,每个节点也有两样东西:数据、指针。

座位就是节点,人就是数据,链子就是指针。

这个结构的关键是这条被叫做“指针”的链子,它包含着在何处能找到下一项的信息(确切地说是下一项的地址)。

对于本题而言,节点就是代码中的结构体Node,数据就是data,指针就是next。

struct Node { //定义了一个结构体Node
int data; //数据成员1:整型data,用于存储节点的数据
Node* next; //数据成员2:指向Node类型的指针next,用于指向下一节点
};

每个节点的指针next指向下一个节点的地址,这样就做成了链子。

要操作链表,必须先要找到领头的,才能顺藤摸瓜,因此需要给链表设置一个头指针(head pointer)。要判断什么时候摸到了队尾,需要有个特定的标志,方案是把最后一个节点的next指针设为NULL(表示空指针,可以把它理解为指针型的0)。

指针说到底也是变量,它的值其实就是整数,只不过这个整数用于表示内存的地址。上图是链表的逻辑结构,它在内存中是类似这样的:

定义头指针只需要一行代码:

Node* head; //定义一个指向Node类型的指针head

当然还要将第一个节点的地址赋给head;

head=firstNode;

三、链表的插入操作

要插入一个节点,只需要做两件事:将要插的节点指向下一个节点,上一个节点指向要插的节点。

下一个节点的地址是存在原链表的上一个节点中的,所以,只要知道上一个节点的地址,就能直接插入节点。比如将指针s指向的节点插入到指针p指向的节点之后:

1.本题的链表插入解析

本题要求在第一个节点之前插入节点,也就是在head之后插入节点。head指针指向的是原链表第一个节点,因此,只需要将head指针的值赋给新节点的next指针,再把新节点的地址赋给head即可完成插入。代码如下:

Node* newNode = new Node; //创建新节点newNode,并为其分配内存
newNode->data = 42;  //将新的节点的数据成员 data 赋值为 42
newNode->next = head;  //将head指针的值赋给新节点的next指针,即将原第一个节点的地址赋给新节点的next指针
head = newNode; //新节点的地址赋给head,即使head指向新节点

故选A。

其他选项:

B. 代码head->data = 42; 是错误的,head指向的是原链表的第一个节点,因此它会将42赋给了原链表的第一个节点,而不是新插入的节点。

C. 代码没有用head指针指向新节点,注意因为head指针本来是指向第一个节点的,所以head->next = newNode;是将第一个节点的next指针指向新节点,这相当于把新节点放在第一个节点之后,插错了位置。而且这个新节点只连了前面,没连后面。

D. 与选项C相反,新节点只连了后面,没连前面。

2.模拟真实链表插入

(1)添加链表元素。

要在链表中插入一个节点,首先链表中得有元素,所以先要在链表中添加元素。

添加元素主要有三步:

①创建节点。创建新节点newNode,并为其分配内存空间,如前面的Node* newNode = new Node;

②为节点赋值。分数据和指针两部分,为节点存入数据如前面的newNode->data = 42; 因为添加的节点是处于最后的,所以其next指针应设为NULL。

③链接节点。如果是第一次添加,就和head链接,否则就和尾节点链接。

代码如下:

//为链表添加元素
Node *tail, *newNode; //tail指向尾节点,newNode指向要添加的节点
head = NULL;
int t[]={9527, 9421, 2587, 2627, 1372, 1573, 1920};
for(int i=0; i<7; i++){
    //创建节点
    newNode = new Node; //创建新节点newNode,并为其分配内存

    //为节点赋值
    newNode->data = t[i]; //将新的节点的数据成员 data 赋值
    newNode->next = NULL; //将当前节点的next指针设为空指针

    //链接节点
    if(head==NULL) head = newNode; //为头指针赋值
    else tail->next = newNode; //为上个节点的next指针赋值
	
    tail=newNode; //更新尾节点,将新节点赋给tail
}

代码说明:

①三个指针。为方便添加链表元素,需要3个指针:head、tail、newNode。head即是前面说的头指针,指向第一个节点;tail是尾指针,指向最后一个节点,有了这个指针才能方便在队尾添加新元素;newNode指向要添加的节点。

②指针的赋值。关于这三个指针的赋值要特别注意:head指针最开始赋值为NULL,表示链表是空的,当添加第一个元素时,就指向第一个元素。新添加的节点自然处于队尾,所以其next指针应设为NULL。tail的next指针指向新节点,并且每次添加完节点,别忘了还要更新tail(把新节点赋给tail)。

(2)插入节点

方法是通过遍历找到节点要插入的位置,然后用前面介绍的插入方法插入节点。

插入位置有两种情况:插入到第一个节点前、插入到其他节点前。前者涉及到head指针,与后者的处理是不一样的。

代码如下:

//链表插入节点的函数:在第i个节点前插入data
void ListInsert(Node** head, int i, int data){
    //找到要插入的节点的前一个节点p
    Node* p;
    p = *head;
    int j=1;
    while(p&&j<i-1){
        p=p->next; //指针移到下个节点
        j++;
    }

    Node* newNode = new Node;
    newNode->data = data;
    if(i==1){ //处理插入到第一个节点前的情况
        newNode->next = *head;
        *head = newNode;
    }else{ //处理插入到其他节点前的情况
        newNode->next = p->next;
        p->next = newNode;
    }
}

代码说明:

① 遍历条件判定。变量j 表示要遍历的节点位置,它应该遍历到i-2的位置终止,此时通过p=p->next 正好将p指向第i-1个节点。while(p&&j<i-1)中的p是为了保证遍历到空指针时停止。

②指针的指针。插入到第一个节点时,需要更新head指针。这时将head作为参数传入函数时,就需要传入head指针的地址,也就是指针的指针。所以函数参数要用Node** head的形式,更新head的值时要用*head,而当调用该函数时参数要用&head。(如果不习惯于指针的指针这种表示,可以用C++的引用语法,只需要在定义函数时将Node** head改为Node* &head即可)

完整的程序代码如下:

#include<stdio.h>
struct Node{
    int data;
    Node* next;
};
Node* head;

//链表插入节点的函数:在第i个节点前插入data
void ListInsert(Node** head, int i, int data){
    //找到要插入的节点的前一个节点p
    Node* p;
    p = *head;
    int j=1;
    while(p&&j<i-1){
        p=p->next; //指针移到下个节点
        j++;
    }

    Node* newNode = new Node;
    newNode->data = data;
    if(i==1){ //处理插入到第一个节点前的情况
        newNode->next = *head;
        *head = newNode;
    }else{ //处理插入到其他节点前的情况
        newNode->next = p->next;
        p->next = newNode;
    }
}

int main(){
    //为链表添加元素
    Node *tail, *newNode; //tail指向尾节点,newNode指向要添加的节点
    head = NULL;
    int t[]={9527, 9421, 2587, 2627, 1372, 1573, 1920};
    for(int i=0; i<7; i++){
        //创建节点
        newNode = new Node; //创建新节点newNode,并为其分配内存

        //为节点赋值
        newNode->data = t[i]; //将新的节点的数据成员 data 赋值
        newNode->next = NULL; //将当前节点的next指针设为空指针

        //链接节点
        if(head==NULL) head = newNode; //为头指针赋值
        else tail->next = newNode; //为上个节点的next指针赋值

        tail=newNode; //更新尾节点,将新节点赋给tail
    }

    //在链表中插入节点
    ListInsert(&head, 1, 42); //在第1个节点前插入数值42

    //打印链表
    Node *current = head;
    while(current){
        printf("%d\n", current->data);
        current = current->next; //指针移到下个节点
    }
    return 0;
}

【题目来源】

2023 CCF非专业级别软件能力认证第一轮 (CSP-J1) 入门级C++语言试题 第4题

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

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

相关文章

LLM和VLM算法常见面试题

LLM相关问题&#xff1a; Bert和GPT的区别 Bert和GPT是两种不同类型的自然语言处理&#xff08;NLP&#xff09;模型&#xff0c;具有一些重要的区别。 模型架构&#xff1a;Bert是基于Transformer架构的模型&#xff0c;它是一个预训练的双向编码器。GPT也是基于Transformer…

【最大的和】

题目 思路 最大序列和的一般做法 dp前缀最大值 dp尾缀最大值 枚举分界点 代码 #include <bits/stdc.h> using namespace std;const int N 5e410; int a[N]; int um[N], dm[N], s; int main() {int t;cin >> t;um[0] INT_MIN;while(t--){int n;cin >> …

【Linux操作系统】进程间通信(1)

目录 一、认识进程间通信二、匿名管道三、命名管道 一、认识进程间通信 进程间不能直接传递数据&#xff0c;因为进程具有独立性&#xff0c;直接传递会破坏进程的独立性。 进程间通信是什么&#xff1f; 一个进程把自己的数据交给另一个进程。 为什么要有进程间通信&#xf…

ThreadLocal解惑

目录 1、ThreadLocal是什么? 2、ThreadLocal实现原理 3、设置线程变量的2种方式 4、关于ThreadLocal的内存泄漏问题 5、使用过程中的注意事项和误区 1、ThreadLocal是什么? 比较书面的回答&#xff1a; 类如其名&#xff0c;线程本地变量。当使用 ThreadLocal 维护变量时…

防爆巡检机器人:工业安全领域的璀璨明星

在当今快速发展的工业领域&#xff0c;安全与效率是企业追求的双核动力。特别是在石油、化工、钢铁冶金、燃气等高风险、高爆炸性的行业中&#xff0c;如何确保生产环境的绝对安全&#xff0c;同时提升巡检效率&#xff0c;成为了企业亟需解决的重大课题。正是在这样的背景下&a…

leetcode 438 找到字符串中所有字母异位词

leetcode 438 找到字符串中所有字母异位词 正文 正文 本题和 leetcode 49 字母异位分词 有些类似&#xff0c;只是 49 题中要求我们找出所有的异位词并进行存储&#xff0c;而本题我们只需要找出异位词对应的索引值。因此&#xff0c;我们无需用到字典&#xff0c;只需使用列表…

理解线程 ID 和 LWP

序言 在不同的系统中&#xff0c;为了更好地管理用户可能会采取不同的编号。比如在学校的教务系统中&#xff0c;管理学生使用的是学号&#xff1b;但是在住宿系统中&#xff0c;为了更加方便的获取一个学生的寝室信息&#xff0c;可能会采取结合你是哪一栋&#xff0c;哪一层&…

MindSearch 部署的到 Hugging Face Space

和原有的CPU版本相比区别是把internstudio换成了github codespace。 随着硅基流动提供了免费的 InternLM2.5-7B-Chat 服务&#xff08;免费的 InternLM2.5-7B-Chat 真的很香&#xff09;&#xff0c;MindSearch 的部署与使用也就迎来了纯 CPU 版本&#xff0c;进一步降低了部署…

【Windows】深度学习环境部署

引言 1 Windows环境准备 1.1 VSCode Visual Studio Code&#xff08;简称 VSCode&#xff09;是一款由微软开发的开源代码编辑器。它非常受开发者欢迎&#xff0c;因为它功能强大、扩展性好&#xff0c;并且支持多种编程语言。VSCode 尤其适合 Python 开发&#xff0c;特别是…

WEB渗透免杀篇-Pezor免杀

往期文章 WEB渗透免杀篇-免杀工具全集-CSDN博客 WEB渗透免杀篇-加载器免杀-CSDN博客 WEB渗透免杀篇-分块免杀-CSDN博客 WEB渗透免杀篇-Powershell免杀-CSDN博客 WEB渗透免杀篇-Python源码免杀-CSDN博客 WEB渗透免杀篇-C#源码免杀-CSDN博客 WEB渗透免杀篇-MSFshellcode免杀…

文心一言 VS 讯飞星火 VS chatgpt (331)-- 算法导论22.5 7题

七、给定有向图 G ( V &#xff0c; E ) G(V&#xff0c;E) G(V&#xff0c;E)&#xff0c;如果对于所有结点对 u , v ∈ V u,v∈V u,v∈V,我们有 u → v u→v u→v或 v → u v→u v→u&#xff0c;则 G G G是半连通的。请给出一个有效的算法来判断图 G G G是否是半连通的。证…

根据需求、质量属性描述和架构特性开发一套公路桥梁在线管理系统

目录 案例 【题目】 【问题 1】(12 分) 【问题 2】(13 分) 答案 【问题 1】答案 【问题 2】答案 相关推荐 案例 阅读以下关于软件架构评估的叙述&#xff0c;在答题纸上回答问题 1 和问题 2。 【题目】 某单位为了建设健全的公路桥梁养护管理档案&#xff0c;拟开发一套公…

若依框架搭建

一、后端启动 1、git克隆下载前后端分离版本 RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分离权限管理系统&#xff0c;同时提供了 Vue3 的版本 (gitee.com) 2、初始化项目 到springboot后如…

Excel中的“LOOKUP”:熟识四个LOOKUP,可以让数据“查找”得心应手

熟识四个lookup&#xff0c;可以让数据“查找”得心应手。 (笔记模板由python脚本于2024年08月23日 19:27:16创建&#xff0c;本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖…

入门 PyQt6 看过来(项目)29 在线购物-销售分析

销售分析部分包含按月分析的簇状图和按类别分析的饼图&#xff0c;如下&#xff1a; ​ 1 页面设计 设计该页面其实很简单&#xff0c;说白了就是两个也切tab类以及饼图和簇状图。打开QTDesiger&#xff0c;按下图添加控件&#xff0c;并重命名如下&#xff1a; ​ 2 按类…

计算机视觉与视觉大模型对板书检测效果对比

文章目录 计算机视觉火山引擎ocr阿里云ocr 视觉大模型GPT4kimi通义千问chatGLM百度 全部正确某开源模型&#xff0c;效果不佳 计算机视觉 火山引擎ocr 阿里云ocr 视觉大模型 GPT4 kimi 通义千问 chatGLM 百度 全部正确 某开源模型&#xff0c;效果不佳

基于springboot的养老院管理系统的设计与实现 (含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于springboot的养老院管理系统拥有多种角色账号&#xff1a;管理员和用户 管理员&#xff1a;管理员管理、用户管理、健康管理、病例方案管理、药品管理、餐饮管理、外出管理、入住管理…

汇编

汇编指令 随机数mov指令mov指令ldr指令&#xff08;伪指令&#xff09;add指令sub指令bic指令orr指令b指令cmp指令stmfd指令ldmfd指令import栈汇编指令的s后缀 随机数 1.如果某个数的数值范围是0~255之间&#xff0c;那么这个数一定是立即数&#xff1b; 2.把某个数展开成2进制…

树与图的宽度优先遍历

大致思想请参照添加链接描述该篇博客 主要地方的差异就是&#xff1a; 宽度优先遍历就是一层一层的搜索 图中数的层次题目 给定一个 n个点 m条边的有向图&#xff0c;图中可能存在重边和自环。 所有边的长度都是 1&#xff0c;点的编号为 1∼n。 请你求出 1号点到 n号点的…

C++风格指南 2、作用域

2.1. 命名空间 这段文字的关键内容概括如下&#xff1a; 1. 命名空间的使用&#xff1a;除了少数特殊情况外&#xff0c;代码应在命名空间内&#xff0c;命名空间名称应唯一&#xff0c;包含项目名和可选的文件路径。 2. 禁止使用&#xff1a; - using 指令引入整个命名空…