提示:数据结构单链表
目录
前言
看这篇文章的小伙伴还是需要一定的c语言基础的,至少要看懂结构体,看懂申请堆空间。后面的代码里面我也会仔细讲解的
一、单链表的简介
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
链表和顺序表(数组)的优缺点:
数组 优点:系统自己分配空间操作,简单;缺点:进行删除插入操作麻烦。空间利用效率不高。
链表 优点:空间利用率高,增删查改操作都很简单。缺点:算法逻辑相对数组难理解。
二、单链表的基本操作
1.单链表初始化
注:一下所有代码都可以单独运行,我是一步步增加的代码。
链表的初始化是非常重要的,后面的操作都有用到初始化的内容。
链表初始化如图所示:
代码如下:
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//以下代码为打印代码,后面会说
struct student *p=NULL;
p=head;//定义一个结构体指针指向头指针,方便遍历链表
while(p!=NULL) //当前指向不为空
{
printf("第一次打印链表:%d\n",p->data);//打印当前节点存放的数据
p=p->next;//指向下一个节点
}
//*****************************************
}
2.尾部插入(增)
插入可分为尾部插入和中间插入。因为刚初始化链表,链表只有一个节点;所以第二部操作就是尾部插入。
尾部插入如下图所示:
让头节点的指针next 代替 指向新节点的指针newnode 指向 新节点,这样链表就连起来了!注意记得把新节点的next指向NULL;
代码如下(示例):
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//*********尾部插入*********************
struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点
newnode1->data=20;//给第二个节点的data赋值
newnode1->next=NULL;//让第二个节点 的next指向NULL
struct student *p=NULL;//新建一个结构体指针,用于循环遍历
p=head;//p指向头节点
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode1;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************************************
//以下代码为打印代码,后面会说
printf("尾部插入后:\n");
p=head;//p重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//*****************************************
return 0;
}
代码运行效果如下:
可以看到10就是第一个节点(头节点)存放的数据,20则是第二个几点存放的数据
我们还可以再来一次尾部插入试试
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//*********尾部插入*********************
struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点
newnode1->data=20;//给第二个节点的data赋值
newnode1->next=NULL;//让第二个节点 的next指向NULL
struct student *p=NULL;//新建一个结构体指针,用于循环遍历
p=head;//p指向头节点
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode1;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************再来一次尾部插入********
struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点
newnode2->data=30;//给第三个节点的data赋值 我定义为30
newnode2->next=NULL;//让第三个节点 的next指向NULL
p=head;
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode2;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************************************
//以下代码为打印代码,后面会说
printf("第二次尾部插入后:\n");
p=head;//p重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//*****************************************
return 0;
}
运行结果为:
很多朋友到这就会问这样操作链表岂不是很麻烦,其实我是为了方便大家理解,一步步操作的,等大家都有一定了解了后就可以用循环甚至用函数模块化编程就会方便许多。
3.中间插入(增)
会尾部插入之后,我们再来看看中间插入,直接上图:
代码如下:
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//*********尾部插入*********************
struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点
newnode1->data=20;//给第二个节点的data赋值
newnode1->next=NULL;//让第二个节点 的next指向NULL
struct student *p=NULL;//新建一个结构体指针,用于循环遍历
p=head;//p指向头节点
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode1;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************再来一次尾部插入********
struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点
newnode2->data=30;//给第三个节点的data赋值 我定义为30
newnode2->next=NULL;//让第三个节点 的next指向NULL
p=head;
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode2;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************************************
//以下代码为打印代码,后面会说
printf("第二次尾部插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//************中间插入****************
struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点
newnode3->data=40;//给第三个节点的data赋值 我定义为30
newnode3->next=NULL;//让第三个节点 的next指向NULL
int NO;
printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
scanf("%d",&NO);//这样待会输入20吧
p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头
while(p!=NULL)
{
if(p->data==NO)//找到了
break;//退出循环
p=p->next;//没找到,一直向后移动
}
if(p==NULL)
printf("没找到\n");
//找到的话p还指着那个节点的
newnode3->next=p->next;//步骤1 将要插入的新节点的next 指向 要插入的那个节点的后面节点
p->next=newnode3;//步骤2 将要那一个节点的next指向新节点
//以下代码为打印代码,后面会说
printf("中间插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//*****************************************
return 0;
}
运行结果如下:
输入10或者30也是可以的:
4.遍历链表(查)
遍历链表上边的多次用到,原理很简单:就是定义一个结构体变量的指针,先指向头节点,再依次循环下去。如果需要找指定的节点,就需要一个遍历目标,中间插入有用到。
主要代码就是那几行,偷个懒了
5.修改链表节点的值(改)
修改链表节点的值,首先就是要先找到,然后再修改他的data就行了
直接上代码了:
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//*********尾部插入*********************
struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点
newnode1->data=20;//给第二个节点的data赋值
newnode1->next=NULL;//让第二个节点 的next指向NULL
struct student *p=NULL;//新建一个结构体指针,用于循环遍历
p=head;//p指向头节点
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode1;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************再来一次尾部插入********
struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点
newnode2->data=30;//给第三个节点的data赋值 我定义为30
newnode2->next=NULL;//让第三个节点 的next指向NULL
p=head;
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode2;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************************************
//以下代码为打印代码,后面会说
printf("第二次尾部插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//************中间插入****************
struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点
newnode3->data=40;//给第三个节点的data赋值 我定义为30
newnode3->next=NULL;//让第三个节点 的next指向NULL
int NO;
printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
scanf("%d",&NO);//这样待会输入20吧
p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头
while(p!=NULL)
{
if(p->data==NO)//找到了
break;//退出循环
p=p->next;//没找到,一直向后移动
}
if(p==NULL)
printf("没找到\n");
//找到的话p还指着那个节点的
newnode3->next=p->next;//步骤1 将要插入的新节点的next 指向 要插入的那个节点的后面节点
p->next=newnode3;//步骤2 将要那一个节点的next指向新节点
//以下代码为打印代码,后面会说
printf("中间插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//*****************************************
//***************链表节点的修改***********
int last;//存放修改前的值,一会儿会输入
int newnum;//存放心的值
printf("如果你要修改某个节点的值,那请输入他的原本值\n");
scanf("%d",&last);
//遍历
p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头
while(p!=NULL)
{
if(p->data==last)//找到了
break;//退出循环
p=p->next;//没找到,一直向后移动
}
if(p==NULL)
{
printf("没找到\n");
return 0;
}
printf("请输入一个新的值\n");
scanf("%d",&newnum);
p->data= newnum;
//*************************************
//打印
printf("第二次尾部插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
return 0;
}
运行结果如下:
我都是一个代码重复利用的,要是认真看懂的小伙伴应该能看懂吧,如果不妥请私信我改成一个章节写一块代码!
6.删除链表节点(删)
终于来到最后一个删除节点了,删除节点同样需要先找到指定节点。同时要准备两个结构体指针,一前一后,因为断开节点的话,断开节点的前后都找不到对方了,就需要两个指针分别指向断开节点的前后节点,就像两只手一样拉住他们。
1.定义一个结构体指针 first_p 再定义一个结构体指针 second_p
first_p指向头节点,second_p指向头节点的next 如下图所示:
2.这里我让second_p先动,second_p=head->next;这样逻辑比较清晰一点,因为second_p是指向head的next,而head的next是指向第二个节点的,所以second_p=second_p->next之后,second_p就指向第二个节点的next
3.再让first_p动 first_p=head->next;
4.重复2,3步骤,知道second_p指向的节点的data为目标值后进行删除操作
5.删除指定节点原理如图:
如图second_p已经指向第二个节点的next了,也就是second_p已经指向第三个节点了
first_p已经指向第一个节点的next了,也就是first_p已经指向第二个结构体了
那么可以进行 first_p->next=second_p->next;该代码的意思就是第二个节点的next指向第三个节点。
这样就跳过了第三个节点,完成了删除操作
之后可以将second_p指向的删除节点的next指向NULL ,再使用free();函数释放被删除节点所在的堆空间,达到节约空间的目的。
#include <stdio.h>
#include <stdlib.h>
struct student{ //student可以自己定义
int data;//存放数据
struct student *next=NULL;//用于指向下一个节点 也就是下一个结构体的意思
};
int main()
{
//第一步初始化链表,申请堆空间
//下面是申请堆空间的模板 student 是上面结构体定义的类型
struct student *head=(struct student *)malloc(sizeof(struct student));
//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点
head->next=NULL;//还不知道下一个节点,先指向空;
head->data=10;//第一个节点存放数据,我定义为10
//*********尾部插入*********************
struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点
newnode1->data=20;//给第二个节点的data赋值
newnode1->next=NULL;//让第二个节点 的next指向NULL
struct student *p=NULL;//新建一个结构体指针,用于循环遍历
p=head;//p指向头节点
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode1;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************再来一次尾部插入********
struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点
newnode2->data=30;//给第三个节点的data赋值 我定义为30
newnode2->next=NULL;//让第三个节点 的next指向NULL
p=head;
while(p!=NULL)
{
if(p->next==NULL)//如果p当前指向最后一个节点
{
p->next=newnode2;//让最后一个节点的next指向新节点
break;//不退出的话会一直循环
}
p=p->next;//没到最后一个节点就让p指向下一个节点
}
//*************************************
//以下代码为打印代码,后面会说
printf("第二次尾部插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//************中间插入****************
struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点
newnode3->data=40;//给第三个节点的data赋值 我定义为30
newnode3->next=NULL;//让第三个节点 的next指向NULL
int NO;
printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
scanf("%d",&NO);//这样待会输入20吧
p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头
while(p!=NULL)
{
if(p->data==NO)//找到了
break;//退出循环
p=p->next;//没找到,一直向后移动
}
if(p==NULL)
printf("没找到\n");
//找到的话p还指着那个节点的
newnode3->next=p->next;//步骤1 将要插入的新节点的next 指向 要插入的那个节点的后面节点
p->next=newnode3;//步骤2 将要那一个节点的next指向新节点
//以下代码为打印代码,后面会说
printf("中间插入后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//*****************************************
//***************链表节点的修改***********
int last;//存放修改前的值,一会儿会输入
int newnum;//存放心的值
printf("如果你要修改某个节点的值,那请输入他的原本值\n");
scanf("%d",&last);
//遍历
p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头
while(p!=NULL)
{
if(p->data==last)//找到了
break;//退出循环
p=p->next;//没找到,一直向后移动
}
if(p==NULL)
{
printf("没找到\n");
return 0;
}
printf("请输入一个新的值\n");
scanf("%d",&newnum);
p->data= newnum;
//*************************************
//打印
printf("指定修改后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//********指定链表删除*****************
int NO2;
printf("请输入你要删除节点的data:\n");
scanf("%d",&NO2);
struct student *first_p=head;//第一个指针
struct student *second_p=head->next;//第二个指针
//一前一后
while(second_p!=NULL)
{
if(second_p->data==NO2)//找到了
break;//退出
// 没找到继续循环
second_p=second_p->next;
first_p=first_p->next;
}
//找到之后的操作
first_p->next=second_p->next;//被删除节点的上一节点的next 直接指向 被删除节点的下一个节点
second_p->next=NULL;
free(second_p);//释放被删除节点所在的堆空间
//*************************************
//打印
printf("指定删除后:\n");
p=head;//重新指向头节点
while(p!=NULL) //当前指向不为空
{
printf("%d\n",p->data);//打印当前数据
p=p->next;//指向下一个节点
}
//***************************
return 0;
}
总结
好久没有写博客了,有点生疏,由于每个小章节的代码都是在上一个章节的基础上添加的,那在看每个章节的代码时可以找代码最下面可以直接看到核心代码。
最后要想学好链表还是有点难的,在学习单链表的过程中可以自己画一个图辅助自己理解,这样能起到很好的效果!此文章若有错误之处请大家评论或者私信,我好修改,谢谢!