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

【题目】

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

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题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金创想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值