向单向链表插入节点
前言:链表的插入过程就是把新建的节点插入到已有的链表中,鉴于此种理解,也可以把链表的创建看做是一种特殊的插入节点过程,但是具体来说,链表的插入较于链表的创建来说稍复杂一些。
一、问题描述
编写函数,将一个节点插入到一个已有学生链表中,设已有链表按学号有小到大顺序已经排列。
二、算法描述
(1) 输入数据
(2) 生成新节点
(3) 将数据存入新节点
(4) 在链表中寻找第一个大于新节点的学号
注意:该算法的难点是第四步——在链表中寻找第一个大于新节点的学号
凡事查找类问题均存在可查找和不可查找两种情况,不可查找的原因只有一个,那就是链表是空的,可查找也分为两种情况,一是找到了,二是没找到,前者说明找到了第一个比新节点学号大的节点,至于那个结点的位置另当别论,后者说明不存在比新节点学号大的节点,具体可以罗列如下:
三、代码部分
1. structure.h
// Structure
// Created by Lanyan on 20/07/2021.
// JiaoZuo
#ifndef _structure_h
#define _structure_h
#include "stdio.h"
struct stu{ //定义一个包含学号和成绩的结构体
int num; //数据域
float score; //数据域
struct stu *next;//指针域
};
#endif
2. insert.h
// insert a note into a singly linked list
// Created by Lanyan on 20/07/2021.
// JiaoZuo
#ifndef _insert_h
#define _insert_h
#include "structure.h"
struct stu *insert(struct stu *head, struct stu *stud){
//head: the linked list
//stud: the new note
struct stu *p0; //point to the new note
struct stu *p1; //point to the first note gather than to the student number of the new note
struct stu *p2; //point to the previous note of p1
p0 = stud; //initialization
p1 = head;
if(head == NULL){ //attention_1, head or p1?
//solve situation 1
head = p0; //attention_2, head or p1?
p0->next = NULL;
}else{
while((p1 != NULL) && (p0->num >= p1->num)){ //attention_3 p1 or head? sequence?
//search
//the positions of the two conditions can not be reversed
p2 = p1;
p1 = p1->next;
}
if(p1 != NULL){ //attention_4,it can be replace with p0->num < p0->num
if(head == p1) head = p0; //solve situation 2
else p2->next = p0; //solve situation 3
p0->next = p1;
}else{ //solve situation 4
p2->next = p0;
p0->next = NULL;
}
}
return head;
}
#endif _insert_h
四、代码解析
1. 对于单向链表来说,插入为什么需要引入两个工具指针?
(1)插入的话不得不考虑插在哪里的问题,这就必须要精准查找;
(2)考虑到链表的单向性,查找过程只能从前往后,如果只用一个工具指针,即使这个指针找到了对应的节点或指向了NULL,是没法再往回退一步插入新节点的;
(3)这个代码中两个工具指针分别是p1和p2,他们的相对关系是p2始终跟在p1的后边,即p2始终指向p1前边的节点,这样就解决了无法插入新节点的问题,为了方便记忆,可以把p2看作是p1的影子。
2. 指针变量的初始化
(1)初始化过程使p0指向要插入的节点stud,这里当然可以直接使用stud参与后续的过程,并没有什么影响,但stud是个形参,直接使用形参还是找个替代者是个人习惯问题,但我建议使用替代者,可以有效使函数的耦合性与外界降到最低;
(2)p1初始化指向head,这是因为p1要头开始查找。
3. 链表为空情况的解决(situation 1)
(1)开头第一句:这个链表能不能进行查找??!head == NULL 意味着这个链表是空的,无法进行查找,于是就直接把新节点作为链表的头结点,新节点的下一个结点作为NULL,这一步解决了情况1——链表为空;
(2)注释attention1:head能换成p1吗?能,只是不够直观;
(3)注释attention2:head能换成p1吗?不能,首先因为我们最终返回的是head而不是p1, 其次p1只是一个工具指针,在查找过程中会移动,p1移动后,与head没有任何关系。
4. 如果可查找,如何进行定位?
1. 注释attention_3:p1能换成head吗?不能,p1是不断进行定位移动的,换成head就没法对移动的情况进行判断;
2. while中的两个情况判定顺序能颠倒吗?不能,先判断p1此时是不是空,非空的话再比较p1此时指定节点的学号大小,这样才有意义;
3. 如果一轮没有找到,则p1向后移动一个节点,p2也后移动一个节点,移动后的p2刚好指在p1刚才指定的节点上。
5. 从while循环跳出来的原因有哪些?
1.因为p0->num >= p1->num(相当于p1 != NULL)跳出循环,这说明在链表中找到了符合的节点,直接把节点插在链表中;
2. 既然找到了节点,那这个结点是头结点还是中间节点?毕竟插入方法不一样,所以要区别对待;所以代码中就分别出现了solve situation 2和solve situation 3,分别代表新节点插入作表头,新节点插入作节点;
4. 因为p1为空跳出循环,这说明p1一下子找到了链表尾也没有找到符合的节点,因此需要把节点插在链表尾,也就解决了situation 4。
以上是本次的全部内容,如果有其他部分不明白的可以参考一下我的
编译预处理——文件包含
C语言基础——单向链表的创建
顺序访问链表中的节点——C语言基础
当然,也可以在评论区留言,我一定回复。