2、单链表的算法之插入节点
2.1、继续上节,访问链表中各个节点的数据
(1)只能用头指针,不能用各个节点自己的指针。因为在实际当中我们保存链表的时候是不会保存各个节点的指针的,只能通过头指针来访问链表节点。
(2)前一个节点内部的pNext指针能帮助我们找到下一个节点。
2.2、将创建节点的代码封装成一个函数
(1)封装时的关键点就是函数的接口(函数参数和返回值)的设计
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
//构建一个节点
struct node
{
int data; //有效数据
struct node*pnext; //指向下一个节点的指针
};
//作用:创建一个链表的节点
//返回值:指针,指针指向我们本函数新创建的一个首地址
void node *create_node(int data)
{
//创建一个链表节点
struct node *p=(struct node*)malloc(sizeof(struct node));
if(NULL==p)
{
printf("malloc error:\n");
return NULL;
}
//清理申请到的堆内存
bzero(p, sizeof(struct node));
//填充节点
p->data=data;
p->pnext=NULL; //将来要指向下一个节点的首地址
//实际操作中将下一个节点malloc返回的指针赋值给这个即可
return p;
}
int main(void)
{
//定义指针
struct node *pHeader=NULL;
//创建一个节点1
pHeader=create_node(1);
//创建第二个节点2
pHeader->pNext=create_node(2);
//创建第三个节点3
pHeader->pNext->pNext=create_node(3);
//访问链表第1个节点的有效数据
printf("node1 data:%d.\n",pHeader->data);
printf("p->data:%d.\n",p->data); //pHeader->data等同于p->data
//访问链表第2个节点的有效数据
printf("node1 data:%d.\n",pHeader->pNext->data);
printf("p->data:%d.\n",p1->data); //pHeader->pNext->data等同于p1->data
//访问链表第3个节点的有效数据
printf("node1 data:%d.\n",pHeader->data);
printf("p->data:%d.\n",p2->data); //pHeader->pNext->data等同于p2->data
}
2.3、从链表头部插入新节点
2.3.1、什么是头节点
(1)问题:因为我们在insert_tail中直接默认了头指针指向的有一个节点,因此如果程序中直接定义了头指针后就直接insert_tail就会报段错误。我们不得不在定义头指针之后先create_node创建一个新节点给头指针初始化,否则不能避免这个错误;但是这样解决让程序看起来逻辑有点不太顺,因为看起来第一个节点和后面的节点的创建、添加方式有点不同。
(2)链表还有另外一种用法,就是把头指针指向的第一个节点作为头节点使用。头节点的特点是:第一,它紧跟在头指针后面。第二,头节点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数),指针部分指向下一个节点,也就是第一个节点。
(3)这样看来,头节点确实和其他节点不同。我们在创建一个链表时添加节点的方法也不同。头节点在创建头指针时一并创建并且和头指针关联起来;后面的真正的存储数据的节点用节点添加的函数来完成,譬如insert_tail.
(4)链表有没有头节点是不同的。体现在链表的插入节点、删除节点、遍历节点、解析链表的各个算法函数都不同。所以如果一个链表设计的时候就有头节点那么后面的所有算法都应该这样来处理;如果设计时就没有头节点,那么后面的所有算法都应该按照没有头节点来做。实际编程中两种链表都有人用,所以大家在看别人写的代码时一定要注意看它有没有头节点。
2.3.2、从链表头部插入新节点
(1)注意写代码过程中的箭头符号,和说话过程中的指针指向。这是两码事,容易搞混。箭头符号实际上是用指针方式来访问结构体,所以箭头符号的实质是访问结构体中的成员。更清楚一点说程序中的箭头和链表的连接没有任何关系;链表中的节点通过指针指向来连接,编程中表现为一个赋值语句(用=来进行连接),实质是把后一个节点的首地址,赋值给前一个节点中的pNext元素做为值。
(2)链表可以从头部插入,也可以从尾部插入。也可以两头插入。头部插入和尾部插入对链表来说几乎没有差别。对链表本身无差别,但是有时候对业务逻辑有差别。
void insert_head(struct node *pH, struct node *new)
{
// 第1步: 新节点的next指向原来的第一个节点
new->pNext = pH->pNext;
// 第2步: 头节点的next指向新节点的地址
pH->pNext = new;
// 第3步: 头节点中的计数要加1
pH->data += 1;
}
2.4、从链表尾部插入新节点
(1)尾部插入简单点,因为前面已经建立好的链表不用动。直接动最后一个就可以了。
//思路:由头指针向后遍历,知道走到原来的最后一个节点。
// 原来的最后一个节点里面的pNext是NULL,现在我们只要将它改成new就可以了,添加之后新节点就变成了最后一个。
void insert_tail(struct node *pH, struct node *new)
{
int cnt = 0;
// 分两步来完成插入
// 第一步,先找到链表中最后一个节点
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext; // 往后走一个节点
cnt++;
}
// 第二步,将新节点插入到最后一个节点尾部
p->pNext = new;
pH->data = cnt + 1;
}